Variation switching when deleting (#40780)
* Add variation switcher hook for easier use across multiple components * Add unregister function for validation * Add changelogs * Remove stray console * Add unRegisterValidator function
This commit is contained in:
parent
b4474fc633
commit
7f25060044
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add unregister function to the validation provider and trigger this from the useValidation hook.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add useVariationSwitcher hook and make use of it in the VariationSwitcherFooter.
|
|
@ -6,64 +6,46 @@ import { Button } from '@wordpress/components';
|
|||
import { createElement } from '@wordpress/element';
|
||||
import { arrowLeft, arrowRight, Icon } from '@wordpress/icons';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { Product, ProductVariation } from '@woocommerce/data';
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SwitcherLoadingPlaceholder } from './switcher-loading-placeholder';
|
||||
import { VariationImagePlaceholder } from './variation-image-placeholder';
|
||||
import { useVariationSwitcher } from '../../hooks/use-variation-switcher';
|
||||
|
||||
export type VariationSwitcherProps = {
|
||||
productType?: string;
|
||||
parentProductType?: string;
|
||||
variationId: number;
|
||||
parentId: number;
|
||||
};
|
||||
|
||||
function getVariationName( variation: ProductVariation ): string {
|
||||
return variation.attributes.map( ( attr ) => attr.option ).join( ', ' );
|
||||
}
|
||||
|
||||
export function VariationSwitcherFooter( {
|
||||
parentProductType,
|
||||
variationId,
|
||||
parentId,
|
||||
}: VariationSwitcherProps ) {
|
||||
const {
|
||||
previousVariation,
|
||||
nextVariation,
|
||||
numberOfVariations,
|
||||
...variationIndexes
|
||||
} = useSelect(
|
||||
( select ) => {
|
||||
const { getEntityRecord } = select( 'core' );
|
||||
const parentProduct = getEntityRecord< Product >(
|
||||
'postType',
|
||||
'product',
|
||||
parentId
|
||||
);
|
||||
if ( parentProduct && parentProduct.variations ) {
|
||||
const activeVariationIndex =
|
||||
parentProduct.variations.indexOf( variationId );
|
||||
const previousVariationIndex =
|
||||
activeVariationIndex > 0
|
||||
? activeVariationIndex - 1
|
||||
: parentProduct.variations.length - 1;
|
||||
const nextVariationIndex =
|
||||
activeVariationIndex !== parentProduct.variations.length - 1
|
||||
? activeVariationIndex + 1
|
||||
: 0;
|
||||
const previousVariationId =
|
||||
parentProduct.variations[ previousVariationIndex ];
|
||||
const nextVariationId =
|
||||
parentProduct.variations[ nextVariationIndex ];
|
||||
|
||||
return {
|
||||
nextVariationId,
|
||||
previousVariationId,
|
||||
activeVariationIndex,
|
||||
nextVariationIndex,
|
||||
previousVariationIndex,
|
||||
numberOfVariations: parentProduct.variations.length,
|
||||
goToNextVariation,
|
||||
goToPreviousVariation,
|
||||
} = useVariationSwitcher( {
|
||||
variationId,
|
||||
parentId,
|
||||
parentProductType,
|
||||
} );
|
||||
const { previousVariation, nextVariation } = useSelect(
|
||||
( select ) => {
|
||||
const { getEntityRecord } = select( 'core' );
|
||||
if ( numberOfVariations && numberOfVariations > 0 ) {
|
||||
return {
|
||||
previousVariation: getEntityRecord< ProductVariation >(
|
||||
'postType',
|
||||
'product_variation',
|
||||
|
@ -78,36 +60,26 @@ export function VariationSwitcherFooter( {
|
|||
}
|
||||
return {};
|
||||
},
|
||||
[ variationId, parentId ]
|
||||
[ nextVariationId, previousVariationId, numberOfVariations ]
|
||||
);
|
||||
function onPrevious() {
|
||||
recordEvent( 'product_variation_switch_previous', {
|
||||
variation_length: numberOfVariations,
|
||||
variation_id: previousVariation?.id,
|
||||
variation_index: variationIndexes.activeVariationIndex,
|
||||
previous_variation_index: variationIndexes.previousVariationIndex,
|
||||
} );
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
{},
|
||||
`/product/${ parentId }/variation/${ previousVariation?.id }`
|
||||
),
|
||||
variation_index: activeVariationIndex,
|
||||
previous_variation_index: previousVariationIndex,
|
||||
} );
|
||||
goToPreviousVariation();
|
||||
}
|
||||
|
||||
function onNext() {
|
||||
recordEvent( 'product_variation_switch_next', {
|
||||
variation_length: numberOfVariations,
|
||||
variation_id: nextVariation?.id,
|
||||
variation_index: variationIndexes.activeVariationIndex,
|
||||
next_variation_index: variationIndexes.nextVariationIndex,
|
||||
} );
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
{},
|
||||
`/product/${ parentId }/variation/${ nextVariation?.id }`
|
||||
),
|
||||
variation_index: activeVariationIndex,
|
||||
next_variation_index: nextVariationIndex,
|
||||
} );
|
||||
goToNextVariation();
|
||||
}
|
||||
|
||||
if ( ! numberOfVariations || numberOfVariations < 2 ) {
|
||||
|
@ -132,7 +104,7 @@ export function VariationSwitcherFooter( {
|
|||
) : (
|
||||
<VariationImagePlaceholder className="woocommerce-product-variation-switcher-footer__product-image" />
|
||||
) }
|
||||
{ getVariationName( previousVariation ) }
|
||||
{ previousVariation.name }
|
||||
</Button>
|
||||
) : (
|
||||
<SwitcherLoadingPlaceholder position="left" />
|
||||
|
@ -143,7 +115,7 @@ export function VariationSwitcherFooter( {
|
|||
label={ __( 'Next', 'woocommerce' ) }
|
||||
onClick={ onNext }
|
||||
>
|
||||
{ getVariationName( nextVariation ) }
|
||||
{ nextVariation.name }
|
||||
{ nextVariation.image ? (
|
||||
<img
|
||||
alt={ nextVariation.image.alt || '' }
|
||||
|
|
|
@ -11,6 +11,7 @@ export type ValidationContextProps< T > = {
|
|||
validatorId: string,
|
||||
validator: Validator< T >
|
||||
): React.Ref< HTMLElement >;
|
||||
unRegisterValidator( validatorId: string ): void;
|
||||
validateField( name: string ): ValidatorResponse;
|
||||
validateAll( newData?: Partial< T > ): Promise< ValidationErrors >;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useContext, useMemo, useState } from '@wordpress/element';
|
||||
import { useContext, useMemo, useState, useEffect } from '@wordpress/element';
|
||||
import { DependencyList } from 'react';
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,12 @@ export function useValidation< T >(
|
|||
[ validatorId, ...deps ]
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
return () => {
|
||||
context.unRegisterValidator( validatorId );
|
||||
};
|
||||
}, [] );
|
||||
|
||||
return {
|
||||
ref,
|
||||
error: context.errors[ validatorId ],
|
||||
|
|
|
@ -13,6 +13,7 @@ export const ValidationContext = createContext< ValidationContextProps< any > >(
|
|||
{
|
||||
errors: {},
|
||||
registerValidator: () => () => {},
|
||||
unRegisterValidator: () => () => {},
|
||||
validateField: () => Promise.resolve( undefined ),
|
||||
validateAll: () => Promise.resolve( {} ),
|
||||
}
|
||||
|
|
|
@ -38,6 +38,15 @@ export function ValidationProvider< T >( {
|
|||
};
|
||||
}
|
||||
|
||||
function unRegisterValidator( validatorId: string ): void {
|
||||
if ( validatorsRef.current[ validatorId ] ) {
|
||||
delete validatorsRef.current[ validatorId ];
|
||||
}
|
||||
if ( fieldRefs.current[ validatorId ] ) {
|
||||
delete fieldRefs.current[ validatorId ];
|
||||
}
|
||||
}
|
||||
|
||||
async function validateField(
|
||||
validatorId: string,
|
||||
newData?: Partial< T >
|
||||
|
@ -89,6 +98,7 @@ export function ValidationProvider< T >( {
|
|||
value={ {
|
||||
errors,
|
||||
registerValidator,
|
||||
unRegisterValidator,
|
||||
validateField,
|
||||
validateAll,
|
||||
} }
|
||||
|
|
|
@ -2,3 +2,4 @@ export { useProductHelper as __experimentalUseProductHelper } from './use-produc
|
|||
export { useFeedbackBar as __experimentalUseFeedbackBar } from './use-feedback-bar';
|
||||
export { useVariationsOrder as __experimentalUseVariationsOrder } from './use-variations-order';
|
||||
export { useCurrencyInputProps as __experimentalUseCurrencyInputProps } from './use-currency-input-props';
|
||||
export { useVariationSwitcher as __experimentalUseVariationSwitcher } from './use-variation-switcher';
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
||||
|
||||
type VariationSwitcherProps = {
|
||||
parentProductType?: string;
|
||||
variationId?: number;
|
||||
parentId?: number;
|
||||
};
|
||||
|
||||
export function useVariationSwitcher( {
|
||||
variationId,
|
||||
parentId,
|
||||
parentProductType,
|
||||
}: VariationSwitcherProps ) {
|
||||
const { invalidateResolution } = useDispatch( 'core' );
|
||||
const variationValues = useSelect(
|
||||
( select ) => {
|
||||
if ( parentId === undefined ) {
|
||||
return {};
|
||||
}
|
||||
const { getEntityRecord } = select( 'core' );
|
||||
const parentProduct = getEntityRecord< Product >(
|
||||
'postType',
|
||||
parentProductType || 'product',
|
||||
parentId
|
||||
);
|
||||
if (
|
||||
variationId !== undefined &&
|
||||
parentProduct &&
|
||||
parentProduct.variations
|
||||
) {
|
||||
const activeVariationIndex =
|
||||
parentProduct.variations.indexOf( variationId );
|
||||
const previousVariationIndex =
|
||||
activeVariationIndex > 0
|
||||
? activeVariationIndex - 1
|
||||
: parentProduct.variations.length - 1;
|
||||
const nextVariationIndex =
|
||||
activeVariationIndex !== parentProduct.variations.length - 1
|
||||
? activeVariationIndex + 1
|
||||
: 0;
|
||||
|
||||
return {
|
||||
activeVariationIndex,
|
||||
nextVariationIndex,
|
||||
previousVariationIndex,
|
||||
numberOfVariations: parentProduct.variations.length,
|
||||
previousVariationId:
|
||||
parentProduct.variations[ previousVariationIndex ],
|
||||
nextVariationId:
|
||||
parentProduct.variations[ nextVariationIndex ],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
[ variationId, parentId ]
|
||||
);
|
||||
|
||||
function invalidateVariationList() {
|
||||
invalidateResolution( 'getEntityRecord', [
|
||||
'postType',
|
||||
parentProductType || 'product',
|
||||
parentId,
|
||||
] );
|
||||
}
|
||||
|
||||
function goToNextVariation() {
|
||||
if ( variationValues.nextVariationId === undefined ) {
|
||||
return false;
|
||||
}
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
{},
|
||||
`/product/${ parentId }/variation/${ variationValues.nextVariationId }`
|
||||
),
|
||||
} );
|
||||
}
|
||||
|
||||
function goToPreviousVariation() {
|
||||
if ( variationValues.previousVariationId === undefined ) {
|
||||
return false;
|
||||
}
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
{},
|
||||
`/product/${ parentId }/variation/${ variationValues.previousVariationId }`
|
||||
),
|
||||
} );
|
||||
}
|
||||
|
||||
return {
|
||||
...variationValues,
|
||||
invalidateVariationList,
|
||||
goToNextVariation,
|
||||
goToPreviousVariation,
|
||||
};
|
||||
}
|
|
@ -11,7 +11,10 @@ import {
|
|||
ProductVariation,
|
||||
} from '@woocommerce/data';
|
||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
||||
import { RemoveConfirmationModal } from '@woocommerce/product-editor';
|
||||
import {
|
||||
RemoveConfirmationModal,
|
||||
__experimentalUseVariationSwitcher as useVariationSwitcher,
|
||||
} from '@woocommerce/product-editor';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
|
@ -29,6 +32,12 @@ export const DeleteVariationMenuItem = ( {
|
|||
|
||||
const variationId = useEntityId( 'postType', 'product_variation' );
|
||||
|
||||
const { invalidateVariationList, goToNextVariation, numberOfVariations } =
|
||||
useVariationSwitcher( {
|
||||
parentId: productId ? parseInt( productId, 10 ) : undefined,
|
||||
variationId,
|
||||
} );
|
||||
|
||||
const [ name ] = useEntityProp< string >(
|
||||
'postType',
|
||||
'product_variation',
|
||||
|
@ -82,9 +91,14 @@ export const DeleteVariationMenuItem = ( {
|
|||
setShowModal( false );
|
||||
onClose();
|
||||
|
||||
invalidateVariationList();
|
||||
if ( numberOfVariations && numberOfVariations > 1 ) {
|
||||
goToNextVariation();
|
||||
} else {
|
||||
navigateTo( {
|
||||
url: getNewPath( {}, `/product/${ productId }` ),
|
||||
} );
|
||||
}
|
||||
} )
|
||||
.catch( () => {
|
||||
createErrorNotice(
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Redirect to next variation if deleting a variation on the edit variation page.
|
Loading…
Reference in New Issue