2023-08-25 18:42:31 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2023-11-21 10:46:15 +00:00
|
|
|
import { store, getContext as getContextFn } from '@woocommerce/interactivity';
|
|
|
|
import { StorePart } from '@woocommerce/utils';
|
|
|
|
|
|
|
|
export interface ProductGalleryContext {
|
|
|
|
selectedImage: string;
|
|
|
|
firstMainImageId: string;
|
|
|
|
imageId: string;
|
|
|
|
visibleImagesIds: string[];
|
|
|
|
dialogVisibleImagesIds: string[];
|
|
|
|
isDialogOpen: boolean;
|
|
|
|
productId: string;
|
2023-08-25 18:42:31 +00:00
|
|
|
}
|
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
const getContext = ( ns?: string ) =>
|
|
|
|
getContextFn< ProductGalleryContext >( ns );
|
|
|
|
|
|
|
|
type Store = typeof productGallery & StorePart< ProductGallery >;
|
|
|
|
const { state } = store< Store >( 'woocommerce/product-gallery' );
|
|
|
|
|
|
|
|
const selectImage = (
|
|
|
|
context: ProductGalleryContext,
|
|
|
|
select: 'next' | 'previous'
|
|
|
|
) => {
|
|
|
|
const imagesIds =
|
|
|
|
context[
|
|
|
|
context.isDialogOpen ? 'dialogVisibleImagesIds' : 'visibleImagesIds'
|
|
|
|
];
|
|
|
|
const selectedImageIdIndex = imagesIds.indexOf( context.selectedImage );
|
|
|
|
const nextImageIndex =
|
|
|
|
select === 'next'
|
|
|
|
? Math.min( selectedImageIdIndex + 1, imagesIds.length - 1 )
|
|
|
|
: Math.max( selectedImageIdIndex - 1, 0 );
|
|
|
|
context.selectedImage = imagesIds[ nextImageIndex ];
|
|
|
|
};
|
|
|
|
|
|
|
|
const closeDialog = ( context: ProductGalleryContext ) => {
|
|
|
|
context.isDialogOpen = false;
|
|
|
|
// Reset the main image.
|
|
|
|
context.selectedImage = context.firstMainImageId;
|
|
|
|
};
|
|
|
|
|
|
|
|
const productGallery = {
|
|
|
|
state: {
|
|
|
|
get isSelected() {
|
|
|
|
const { selectedImage, imageId } = getContext();
|
|
|
|
return selectedImage === imageId;
|
|
|
|
},
|
|
|
|
get pagerDotFillOpacity(): number {
|
|
|
|
return state.isSelected ? 1 : 0.2;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
actions: {
|
|
|
|
closeDialog: () => {
|
|
|
|
const context = getContext();
|
|
|
|
closeDialog( context );
|
|
|
|
},
|
|
|
|
openDialog: () => {
|
|
|
|
const context = getContext();
|
|
|
|
context.isDialogOpen = true;
|
|
|
|
},
|
|
|
|
selectImage: () => {
|
|
|
|
const context = getContext();
|
|
|
|
context.selectedImage = context.imageId;
|
|
|
|
},
|
|
|
|
selectNextImage: ( event: MouseEvent ) => {
|
|
|
|
event.stopPropagation();
|
|
|
|
const context = getContext();
|
|
|
|
selectImage( context, 'next' );
|
|
|
|
},
|
|
|
|
selectPreviousImage: ( event: MouseEvent ) => {
|
|
|
|
event.stopPropagation();
|
|
|
|
const context = getContext();
|
|
|
|
selectImage( context, 'previous' );
|
|
|
|
},
|
|
|
|
},
|
|
|
|
callbacks: {
|
|
|
|
watchForChangesOnAddToCartForm: () => {
|
|
|
|
const context = getContext();
|
|
|
|
const variableProductCartForm = document.querySelector(
|
|
|
|
`form[data-product_id="${ context.productId }"]`
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( ! variableProductCartForm ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Replace with an interactive block that calls `actions.selectImage`.
|
|
|
|
const observer = new MutationObserver( function ( mutations ) {
|
|
|
|
for ( const mutation of mutations ) {
|
|
|
|
const mutationTarget = mutation.target as HTMLElement;
|
|
|
|
const currentImageAttribute =
|
|
|
|
mutationTarget.getAttribute( 'current-image' );
|
|
|
|
if (
|
|
|
|
mutation.type === 'attributes' &&
|
|
|
|
currentImageAttribute &&
|
|
|
|
context.visibleImagesIds.includes(
|
|
|
|
currentImageAttribute
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
context.selectedImage = currentImageAttribute;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} );
|
2023-08-25 18:42:31 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
observer.observe( variableProductCartForm, {
|
|
|
|
attributes: true,
|
|
|
|
} );
|
2023-09-12 07:36:44 +00:00
|
|
|
|
2023-12-15 21:22:10 +00:00
|
|
|
const clearVariationsLink = document.querySelector(
|
|
|
|
'.wp-block-add-to-cart-form .reset_variations'
|
|
|
|
);
|
|
|
|
|
|
|
|
const selectFirstImage = () => {
|
|
|
|
context.selectedImage = context.firstMainImageId;
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( clearVariationsLink ) {
|
|
|
|
clearVariationsLink.addEventListener(
|
|
|
|
'click',
|
|
|
|
selectFirstImage
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
return () => {
|
|
|
|
observer.disconnect();
|
2023-12-15 21:22:10 +00:00
|
|
|
document.removeEventListener( 'click', selectFirstImage );
|
2023-11-15 21:05:51 +00:00
|
|
|
};
|
2023-11-21 10:46:15 +00:00
|
|
|
},
|
|
|
|
keyboardAccess: () => {
|
|
|
|
const context = getContext();
|
|
|
|
let allowNavigation = true;
|
2023-10-27 17:12:32 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
const handleKeyEvents = ( event: KeyboardEvent ) => {
|
|
|
|
if ( ! allowNavigation || ! context.isDialogOpen ) {
|
2023-10-27 17:12:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
// Disable navigation for a brief period to prevent spamming.
|
|
|
|
allowNavigation = false;
|
2023-10-27 17:12:32 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
requestAnimationFrame( () => {
|
|
|
|
allowNavigation = true;
|
2023-10-27 17:12:32 +00:00
|
|
|
} );
|
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
// Check if the esc key is pressed.
|
|
|
|
if ( event.code === 'Escape' ) {
|
|
|
|
closeDialog( context );
|
|
|
|
}
|
2023-10-23 14:53:58 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
// Check if left arrow key is pressed.
|
|
|
|
if ( event.code === 'ArrowLeft' ) {
|
|
|
|
selectImage( context, 'previous' );
|
|
|
|
}
|
2023-10-23 14:53:58 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
// Check if right arrow key is pressed.
|
|
|
|
if ( event.code === 'ArrowRight' ) {
|
|
|
|
selectImage( context, 'next' );
|
|
|
|
}
|
|
|
|
};
|
2023-10-23 14:53:58 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
document.addEventListener( 'keydown', handleKeyEvents );
|
2023-10-23 14:53:58 +00:00
|
|
|
|
2023-11-21 10:46:15 +00:00
|
|
|
return () =>
|
|
|
|
document.removeEventListener( 'keydown', handleKeyEvents );
|
2023-08-25 18:42:31 +00:00
|
|
|
},
|
|
|
|
},
|
2023-11-21 10:46:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
store( 'woocommerce/product-gallery', productGallery );
|
|
|
|
|
|
|
|
export type ProductGallery = typeof productGallery;
|