/** * External dependencies */ import { store as interactivityApiStore } from '@woocommerce/interactivity'; interface State { [ key: string ]: unknown; } export interface ProductGalleryInteractivityApiContext { woocommerce: { selectedImage: string; imageId: string; visibleImagesIds: string[]; isDialogOpen: boolean; productId: string; }; } export interface ProductGallerySelectors { woocommerce: { isSelected: ( store: unknown ) => boolean; pagerDotFillOpacity: ( store: SelectorsStore ) => number; selectedImageIndex: ( store: SelectorsStore ) => number; isDialogOpen: ( store: unknown ) => boolean; }; } interface Actions { woocommerce: { thumbnails: { handleClick: ( context: ProductGalleryInteractivityApiContext ) => void; }; handlePreviousImageButtonClick: { ( store: Store ): void; }; handleNextImageButtonClick: { ( store: Store ): void; }; }; } interface Store { state: State; context: ProductGalleryInteractivityApiContext; selectors: ProductGallerySelectors; actions: Actions; ref?: HTMLElement; } interface Event { keyCode: number; } type SelectorsStore = Pick< Store, 'context' | 'selectors' | 'ref' >; enum Keys { ESC = 27, LEFT_ARROW = 37, RIGHT_ARROW = 39, } interactivityApiStore( { state: {}, effects: { woocommerce: { watchForChangesOnAddToCartForm: ( store: Store ) => { const variableProductCartForm = document.querySelector( `form[data-product_id="${ store.context.woocommerce.productId }"]` ); if ( ! variableProductCartForm ) { return; } 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 && store.context.woocommerce.visibleImagesIds.includes( currentImageAttribute ) ) { store.context.woocommerce.selectedImage = currentImageAttribute; } } } ); observer.observe( variableProductCartForm, { attributes: true, } ); return () => { observer.disconnect(); }; }, keyboardAccess: ( store: Store ) => { const { context, actions } = store; let allowNavigation = true; const handleKeyEvents = ( event: Event ) => { if ( ! allowNavigation || ! context.woocommerce?.isDialogOpen ) { return; } // Disable navigation for a brief period to prevent spamming. allowNavigation = false; requestAnimationFrame( () => { allowNavigation = true; } ); // Check if the esc key is pressed. if ( event.keyCode === Keys.ESC ) { context.woocommerce.isDialogOpen = false; } // Check if left arrow key is pressed. if ( event.keyCode === Keys.LEFT_ARROW ) { actions.woocommerce.handlePreviousImageButtonClick( store ); } // Check if right arrow key is pressed. if ( event.keyCode === Keys.RIGHT_ARROW ) { actions.woocommerce.handleNextImageButtonClick( store ); } }; document.addEventListener( 'keydown', handleKeyEvents ); }, }, }, selectors: { woocommerce: { isSelected: ( { context }: Store ) => { return ( context?.woocommerce.selectedImage === context?.woocommerce.imageId ); }, pagerDotFillOpacity( store: SelectorsStore ) { const { context } = store; return context?.woocommerce.selectedImage === context?.woocommerce.imageId ? 1 : 0.2; }, isDialogOpen: ( { context }: Store ) => { return context.woocommerce.isDialogOpen; }, }, }, actions: { woocommerce: { thumbnails: { handleClick: ( { context }: Store ) => { context.woocommerce.selectedImage = context.woocommerce.imageId; }, }, dialog: { handleCloseButtonClick: ( { context }: Store ) => { context.woocommerce.isDialogOpen = false; }, }, handleSelectImage: ( { context }: Store ) => { context.woocommerce.selectedImage = context.woocommerce.imageId; }, handleNextImageButtonClick: ( store: Store ) => { const { context } = store; const selectedImageIdIndex = context.woocommerce.visibleImagesIds.indexOf( context.woocommerce.selectedImage ); const nextImageIndex = Math.min( selectedImageIdIndex + 1, context.woocommerce.visibleImagesIds.length - 1 ); context.woocommerce.selectedImage = context.woocommerce.visibleImagesIds[ nextImageIndex ]; }, handlePreviousImageButtonClick: ( store: Store ) => { const { context } = store; const selectedImageIdIndex = context.woocommerce.visibleImagesIds.indexOf( context.woocommerce.selectedImage ); const previousImageIndex = Math.max( selectedImageIdIndex - 1, 0 ); context.woocommerce.selectedImage = context.woocommerce.visibleImagesIds[ previousImageIndex ]; }, }, }, } );