2023-08-25 18:42:31 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
|
|
|
import { store as interactivityApiStore } from '@woocommerce/interactivity';
|
|
|
|
|
|
|
|
interface State {
|
|
|
|
[ key: string ]: unknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Context {
|
2023-08-31 15:13:40 +00:00
|
|
|
woocommerce: {
|
2023-09-12 07:36:44 +00:00
|
|
|
selectedImage: string;
|
|
|
|
imageId: string;
|
2023-10-05 16:55:52 +00:00
|
|
|
visibleImagesIds: string[];
|
2023-09-15 08:54:49 +00:00
|
|
|
isDialogOpen: boolean;
|
2023-10-27 17:12:32 +00:00
|
|
|
productId: string;
|
2023-08-31 15:13:40 +00:00
|
|
|
};
|
2023-08-25 18:42:31 +00:00
|
|
|
}
|
|
|
|
|
2023-10-20 07:42:39 +00:00
|
|
|
export interface ProductGallerySelectors {
|
2023-08-31 15:13:40 +00:00
|
|
|
woocommerce: {
|
2023-09-12 07:36:44 +00:00
|
|
|
isSelected: ( store: unknown ) => boolean;
|
2023-09-21 19:35:25 +00:00
|
|
|
pagerDotFillOpacity: ( store: SelectorsStore ) => number;
|
2023-10-05 16:55:52 +00:00
|
|
|
selectedImageIndex: ( store: SelectorsStore ) => number;
|
2023-09-15 08:54:49 +00:00
|
|
|
isDialogOpen: ( store: unknown ) => boolean;
|
2023-09-12 07:36:44 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Actions {
|
|
|
|
woocommerce: {
|
2023-09-15 08:54:49 +00:00
|
|
|
thumbnails: {
|
|
|
|
handleClick: ( context: Context ) => void;
|
|
|
|
};
|
2023-10-23 14:53:58 +00:00
|
|
|
handlePreviousImageButtonClick: {
|
|
|
|
( store: Store ): void;
|
|
|
|
};
|
|
|
|
handleNextImageButtonClick: {
|
|
|
|
( store: Store ): void;
|
|
|
|
};
|
2023-08-25 18:42:31 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Store {
|
|
|
|
state: State;
|
|
|
|
context: Context;
|
2023-10-20 07:42:39 +00:00
|
|
|
selectors: ProductGallerySelectors;
|
2023-09-12 07:36:44 +00:00
|
|
|
actions: Actions;
|
|
|
|
ref?: HTMLElement;
|
2023-08-25 18:42:31 +00:00
|
|
|
}
|
|
|
|
|
2023-10-23 14:53:58 +00:00
|
|
|
interface Event {
|
|
|
|
keyCode: number;
|
|
|
|
}
|
|
|
|
|
2023-09-21 19:35:25 +00:00
|
|
|
type SelectorsStore = Pick< Store, 'context' | 'selectors' | 'ref' >;
|
|
|
|
|
2023-10-23 14:53:58 +00:00
|
|
|
enum Keys {
|
|
|
|
ESC = 27,
|
|
|
|
LEFT_ARROW = 37,
|
|
|
|
RIGHT_ARROW = 39,
|
|
|
|
}
|
|
|
|
|
2023-08-25 18:42:31 +00:00
|
|
|
interactivityApiStore( {
|
2023-09-12 07:36:44 +00:00
|
|
|
state: {},
|
2023-10-23 14:53:58 +00:00
|
|
|
effects: {
|
|
|
|
woocommerce: {
|
2023-10-27 17:12:32 +00:00
|
|
|
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();
|
|
|
|
};
|
|
|
|
},
|
2023-10-23 14:53:58 +00:00
|
|
|
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 );
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-08-25 18:42:31 +00:00
|
|
|
selectors: {
|
2023-08-31 15:13:40 +00:00
|
|
|
woocommerce: {
|
2023-09-12 07:36:44 +00:00
|
|
|
isSelected: ( { context }: Store ) => {
|
|
|
|
return (
|
|
|
|
context?.woocommerce.selectedImage ===
|
|
|
|
context?.woocommerce.imageId
|
|
|
|
);
|
|
|
|
},
|
2023-09-21 19:35:25 +00:00
|
|
|
pagerDotFillOpacity( store: SelectorsStore ) {
|
|
|
|
const { context } = store;
|
|
|
|
|
|
|
|
return context?.woocommerce.selectedImage ===
|
|
|
|
context?.woocommerce.imageId
|
|
|
|
? 1
|
|
|
|
: 0.2;
|
|
|
|
},
|
2023-09-15 08:54:49 +00:00
|
|
|
isDialogOpen: ( { context }: Store ) => {
|
2023-10-02 12:36:48 +00:00
|
|
|
return context.woocommerce.isDialogOpen;
|
2023-09-15 08:54:49 +00:00
|
|
|
},
|
2023-09-12 07:36:44 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
actions: {
|
|
|
|
woocommerce: {
|
2023-09-15 08:54:49 +00:00
|
|
|
thumbnails: {
|
|
|
|
handleClick: ( { context }: Store ) => {
|
|
|
|
context.woocommerce.selectedImage =
|
|
|
|
context.woocommerce.imageId;
|
|
|
|
},
|
2023-08-25 18:42:31 +00:00
|
|
|
},
|
2023-10-02 12:36:48 +00:00
|
|
|
dialog: {
|
|
|
|
handleCloseButtonClick: ( { context }: Store ) => {
|
|
|
|
context.woocommerce.isDialogOpen = false;
|
|
|
|
},
|
|
|
|
},
|
2023-09-21 19:35:25 +00:00
|
|
|
handleSelectImage: ( { context }: Store ) => {
|
|
|
|
context.woocommerce.selectedImage = context.woocommerce.imageId;
|
|
|
|
},
|
2023-10-05 16:55:52 +00:00
|
|
|
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 ];
|
|
|
|
},
|
2023-08-25 18:42:31 +00:00
|
|
|
},
|
|
|
|
},
|
2023-09-12 07:36:44 +00:00
|
|
|
} );
|