This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migration of Product Collection and Product Button blocks to the new …
…`store()` API (#11558) * Refactor Product Button with new store() API * Use `wc_initial_state` in Product Button * Fix namespace * Remove unnecessary state * Test namespaces in directive paths * Add test context with namespace * Simplify woo-test context * Move addToCart and animations to a file * Do not pass `rawStore` to `afterLoad` callbacks * Move callbacks and actions back to the main file Because the animation was broken. * Remove selectors in favor of state * Use default ns in `getContext` for state and actions * Remove `afterLoad` callback * Remove unnecessary ns * Fix getContext in add-to-cart * Replace namespace and delete unnecessary store * Pass context types only once * Use an alternative for requestIdleCallback * Add previous react code for notices * Add namespace to Product Collection block * Replace getTextButton with getButtonText * Add block name to the ProductCollection namespace * fix style HTML code * Remove circular deps error on the Interactivity API * Product Gallery block: Migrate to new Interactivity API store (#11721) * Migrate Product Gallery block to new Interactivity API store * Fix some references * Add missing data-wc-interactive * Fix an additional namespace * Remove unnecessary click handler * Dialog working * Refactor action names * Reindex PHP array There was some missing indexes, which turned the array into an object in JS. * Remove unused event handlers * Move next/previous logic to external function * Move StorePart util to the types folder * Rename namespace to `woocommerce/product-gallery` * Undo product collection namespace renaming * Remove unnecessary namespace * Don't hide the large image on page load * Minor refactorings * Fix eslint error * Fix php cs errors with spacing and double arrows alignment * Disable no-use-before-define rule for eslint * Disable @typescript-eslint/ban-types rule for eslint * Fix parsed context error in e2e tests * Fix context parser for Thumbnail image * Move store to the top of the frontend file * Add interactivity api utils to the @woocommerce/utils alias * Replace deprecated event attribute --------- Co-authored-by: Luis Herranz <luisherranz@gmail.com> Co-authored-by: David Arenas <david.arenas@automattic.com> Co-authored-by: roykho <roykho77@gmail.com> --------- Co-authored-by: David Arenas <david.arenas@automattic.com> Co-authored-by: Luigi Teschio <gigitux@gmail.com> Co-authored-by: Alexandre Lara <allexandrelara@gmail.com> Co-authored-by: roykho <roykho77@gmail.com>
- Loading branch information
1 parent
8fc2917
commit 9ea4bfe
Showing
15 changed files
with
522 additions
and
691 deletions.
There are no files selected for viewing
411 changes: 176 additions & 235 deletions
411
assets/js/atomic/blocks/product-elements/button/frontend.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,221 +1,151 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { store as interactivityApiStore } from '@woocommerce/interactivity'; | ||
|
||
interface State { | ||
[ key: string ]: unknown; | ||
} | ||
|
||
export interface ProductGalleryInteractivityApiContext { | ||
woocommerce: { | ||
selectedImage: string; | ||
imageId: string; | ||
visibleImagesIds: string[]; | ||
dialogVisibleImagesIds: 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; | ||
import { store, getContext as getContextFn } from '@woocommerce/interactivity'; | ||
import { StorePart } from '@woocommerce/utils'; | ||
|
||
export interface ProductGalleryContext { | ||
selectedImage: string; | ||
imageId: string; | ||
visibleImagesIds: string[]; | ||
dialogVisibleImagesIds: string[]; | ||
isDialogOpen: boolean; | ||
productId: string; | ||
} | ||
|
||
interface Event { | ||
keyCode: number; | ||
} | ||
|
||
type SelectorsStore = Pick< Store, 'context' | 'selectors' | 'ref' >; | ||
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 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(); | ||
context.isDialogOpen = false; | ||
}, | ||
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; | ||
} | ||
} | ||
} ); | ||
|
||
enum Keys { | ||
ESC = 27, | ||
LEFT_ARROW = 37, | ||
RIGHT_ARROW = 39, | ||
} | ||
observer.observe( variableProductCartForm, { | ||
attributes: true, | ||
} ); | ||
|
||
interactivityApiStore( { | ||
state: {}, | ||
effects: { | ||
woocommerce: { | ||
watchForChangesOnAddToCartForm: ( store: Store ) => { | ||
const variableProductCartForm = document.querySelector( | ||
`form[data-product_id="${ store.context.woocommerce.productId }"]` | ||
); | ||
return () => { | ||
observer.disconnect(); | ||
}; | ||
}, | ||
keyboardAccess: () => { | ||
const context = getContext(); | ||
let allowNavigation = true; | ||
|
||
if ( ! variableProductCartForm ) { | ||
const handleKeyEvents = ( event: KeyboardEvent ) => { | ||
if ( ! allowNavigation || ! context.isDialogOpen ) { | ||
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; | ||
} | ||
} | ||
} ); | ||
// Disable navigation for a brief period to prevent spamming. | ||
allowNavigation = false; | ||
|
||
observer.observe( variableProductCartForm, { | ||
attributes: true, | ||
requestAnimationFrame( () => { | ||
allowNavigation = 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.code === 'Escape' ) { | ||
context.isDialogOpen = false; | ||
} | ||
|
||
// 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.code === 'ArrowLeft' ) { | ||
selectImage( context, 'previous' ); | ||
} | ||
|
||
// 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.code === 'ArrowRight' ) { | ||
selectImage( context, 'next' ); | ||
} | ||
}; | ||
|
||
// Check if right arrow key is pressed. | ||
if ( event.keyCode === Keys.RIGHT_ARROW ) { | ||
actions.woocommerce.handleNextImageButtonClick( store ); | ||
} | ||
}; | ||
document.addEventListener( 'keydown', handleKeyEvents ); | ||
|
||
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 imagesIds = | ||
context.woocommerce[ | ||
context.woocommerce.isDialogOpen | ||
? 'dialogVisibleImagesIds' | ||
: 'visibleImagesIds' | ||
]; | ||
const selectedImageIdIndex = imagesIds.indexOf( | ||
context.woocommerce.selectedImage | ||
); | ||
const nextImageIndex = Math.min( | ||
selectedImageIdIndex + 1, | ||
imagesIds.length - 1 | ||
); | ||
|
||
context.woocommerce.selectedImage = imagesIds[ nextImageIndex ]; | ||
}, | ||
handlePreviousImageButtonClick: ( store: Store ) => { | ||
const { context } = store; | ||
const imagesIds = | ||
context.woocommerce[ | ||
context.woocommerce.isDialogOpen | ||
? 'dialogVisibleImagesIds' | ||
: 'visibleImagesIds' | ||
]; | ||
const selectedImageIdIndex = imagesIds.indexOf( | ||
context.woocommerce.selectedImage | ||
); | ||
const previousImageIndex = Math.max( | ||
selectedImageIdIndex - 1, | ||
0 | ||
); | ||
context.woocommerce.selectedImage = | ||
imagesIds[ previousImageIndex ]; | ||
}, | ||
return () => | ||
document.removeEventListener( 'keydown', handleKeyEvents ); | ||
}, | ||
}, | ||
} ); | ||
}; | ||
|
||
store( 'woocommerce/product-gallery', productGallery ); | ||
|
||
export type ProductGallery = typeof productGallery; |
Oops, something went wrong.