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 { createElement } from '@wordpress/element';
|
||||||
import { arrowLeft, arrowRight, Icon } from '@wordpress/icons';
|
import { arrowLeft, arrowRight, Icon } from '@wordpress/icons';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { Product, ProductVariation } from '@woocommerce/data';
|
import { ProductVariation } from '@woocommerce/data';
|
||||||
import { recordEvent } from '@woocommerce/tracks';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { SwitcherLoadingPlaceholder } from './switcher-loading-placeholder';
|
import { SwitcherLoadingPlaceholder } from './switcher-loading-placeholder';
|
||||||
import { VariationImagePlaceholder } from './variation-image-placeholder';
|
import { VariationImagePlaceholder } from './variation-image-placeholder';
|
||||||
|
import { useVariationSwitcher } from '../../hooks/use-variation-switcher';
|
||||||
|
|
||||||
export type VariationSwitcherProps = {
|
export type VariationSwitcherProps = {
|
||||||
productType?: string;
|
parentProductType?: string;
|
||||||
variationId: number;
|
variationId: number;
|
||||||
parentId: number;
|
parentId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getVariationName( variation: ProductVariation ): string {
|
|
||||||
return variation.attributes.map( ( attr ) => attr.option ).join( ', ' );
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariationSwitcherFooter( {
|
export function VariationSwitcherFooter( {
|
||||||
|
parentProductType,
|
||||||
variationId,
|
variationId,
|
||||||
parentId,
|
parentId,
|
||||||
}: VariationSwitcherProps ) {
|
}: VariationSwitcherProps ) {
|
||||||
const {
|
const {
|
||||||
previousVariation,
|
|
||||||
nextVariation,
|
|
||||||
numberOfVariations,
|
numberOfVariations,
|
||||||
...variationIndexes
|
nextVariationId,
|
||||||
} = useSelect(
|
previousVariationId,
|
||||||
( 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 {
|
|
||||||
activeVariationIndex,
|
activeVariationIndex,
|
||||||
nextVariationIndex,
|
nextVariationIndex,
|
||||||
previousVariationIndex,
|
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 >(
|
previousVariation: getEntityRecord< ProductVariation >(
|
||||||
'postType',
|
'postType',
|
||||||
'product_variation',
|
'product_variation',
|
||||||
|
@ -78,36 +60,26 @@ export function VariationSwitcherFooter( {
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
[ variationId, parentId ]
|
[ nextVariationId, previousVariationId, numberOfVariations ]
|
||||||
);
|
);
|
||||||
function onPrevious() {
|
function onPrevious() {
|
||||||
recordEvent( 'product_variation_switch_previous', {
|
recordEvent( 'product_variation_switch_previous', {
|
||||||
variation_length: numberOfVariations,
|
variation_length: numberOfVariations,
|
||||||
variation_id: previousVariation?.id,
|
variation_id: previousVariation?.id,
|
||||||
variation_index: variationIndexes.activeVariationIndex,
|
variation_index: activeVariationIndex,
|
||||||
previous_variation_index: variationIndexes.previousVariationIndex,
|
previous_variation_index: previousVariationIndex,
|
||||||
} );
|
|
||||||
navigateTo( {
|
|
||||||
url: getNewPath(
|
|
||||||
{},
|
|
||||||
`/product/${ parentId }/variation/${ previousVariation?.id }`
|
|
||||||
),
|
|
||||||
} );
|
} );
|
||||||
|
goToPreviousVariation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNext() {
|
function onNext() {
|
||||||
recordEvent( 'product_variation_switch_next', {
|
recordEvent( 'product_variation_switch_next', {
|
||||||
variation_length: numberOfVariations,
|
variation_length: numberOfVariations,
|
||||||
variation_id: nextVariation?.id,
|
variation_id: nextVariation?.id,
|
||||||
variation_index: variationIndexes.activeVariationIndex,
|
variation_index: activeVariationIndex,
|
||||||
next_variation_index: variationIndexes.nextVariationIndex,
|
next_variation_index: nextVariationIndex,
|
||||||
} );
|
|
||||||
navigateTo( {
|
|
||||||
url: getNewPath(
|
|
||||||
{},
|
|
||||||
`/product/${ parentId }/variation/${ nextVariation?.id }`
|
|
||||||
),
|
|
||||||
} );
|
} );
|
||||||
|
goToNextVariation();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! numberOfVariations || numberOfVariations < 2 ) {
|
if ( ! numberOfVariations || numberOfVariations < 2 ) {
|
||||||
|
@ -132,7 +104,7 @@ export function VariationSwitcherFooter( {
|
||||||
) : (
|
) : (
|
||||||
<VariationImagePlaceholder className="woocommerce-product-variation-switcher-footer__product-image" />
|
<VariationImagePlaceholder className="woocommerce-product-variation-switcher-footer__product-image" />
|
||||||
) }
|
) }
|
||||||
{ getVariationName( previousVariation ) }
|
{ previousVariation.name }
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<SwitcherLoadingPlaceholder position="left" />
|
<SwitcherLoadingPlaceholder position="left" />
|
||||||
|
@ -143,7 +115,7 @@ export function VariationSwitcherFooter( {
|
||||||
label={ __( 'Next', 'woocommerce' ) }
|
label={ __( 'Next', 'woocommerce' ) }
|
||||||
onClick={ onNext }
|
onClick={ onNext }
|
||||||
>
|
>
|
||||||
{ getVariationName( nextVariation ) }
|
{ nextVariation.name }
|
||||||
{ nextVariation.image ? (
|
{ nextVariation.image ? (
|
||||||
<img
|
<img
|
||||||
alt={ nextVariation.image.alt || '' }
|
alt={ nextVariation.image.alt || '' }
|
||||||
|
|
|
@ -11,6 +11,7 @@ export type ValidationContextProps< T > = {
|
||||||
validatorId: string,
|
validatorId: string,
|
||||||
validator: Validator< T >
|
validator: Validator< T >
|
||||||
): React.Ref< HTMLElement >;
|
): React.Ref< HTMLElement >;
|
||||||
|
unRegisterValidator( validatorId: string ): void;
|
||||||
validateField( name: string ): ValidatorResponse;
|
validateField( name: string ): ValidatorResponse;
|
||||||
validateAll( newData?: Partial< T > ): Promise< ValidationErrors >;
|
validateAll( newData?: Partial< T > ): Promise< ValidationErrors >;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { useContext, useMemo, useState } from '@wordpress/element';
|
import { useContext, useMemo, useState, useEffect } from '@wordpress/element';
|
||||||
import { DependencyList } from 'react';
|
import { DependencyList } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +23,12 @@ export function useValidation< T >(
|
||||||
[ validatorId, ...deps ]
|
[ validatorId, ...deps ]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
return () => {
|
||||||
|
context.unRegisterValidator( validatorId );
|
||||||
|
};
|
||||||
|
}, [] );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ref,
|
ref,
|
||||||
error: context.errors[ validatorId ],
|
error: context.errors[ validatorId ],
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const ValidationContext = createContext< ValidationContextProps< any > >(
|
||||||
{
|
{
|
||||||
errors: {},
|
errors: {},
|
||||||
registerValidator: () => () => {},
|
registerValidator: () => () => {},
|
||||||
|
unRegisterValidator: () => () => {},
|
||||||
validateField: () => Promise.resolve( undefined ),
|
validateField: () => Promise.resolve( undefined ),
|
||||||
validateAll: () => Promise.resolve( {} ),
|
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(
|
async function validateField(
|
||||||
validatorId: string,
|
validatorId: string,
|
||||||
newData?: Partial< T >
|
newData?: Partial< T >
|
||||||
|
@ -89,6 +98,7 @@ export function ValidationProvider< T >( {
|
||||||
value={ {
|
value={ {
|
||||||
errors,
|
errors,
|
||||||
registerValidator,
|
registerValidator,
|
||||||
|
unRegisterValidator,
|
||||||
validateField,
|
validateField,
|
||||||
validateAll,
|
validateAll,
|
||||||
} }
|
} }
|
||||||
|
|
|
@ -2,3 +2,4 @@ export { useProductHelper as __experimentalUseProductHelper } from './use-produc
|
||||||
export { useFeedbackBar as __experimentalUseFeedbackBar } from './use-feedback-bar';
|
export { useFeedbackBar as __experimentalUseFeedbackBar } from './use-feedback-bar';
|
||||||
export { useVariationsOrder as __experimentalUseVariationsOrder } from './use-variations-order';
|
export { useVariationsOrder as __experimentalUseVariationsOrder } from './use-variations-order';
|
||||||
export { useCurrencyInputProps as __experimentalUseCurrencyInputProps } from './use-currency-input-props';
|
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,
|
ProductVariation,
|
||||||
} from '@woocommerce/data';
|
} from '@woocommerce/data';
|
||||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
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';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
|
@ -29,6 +32,12 @@ export const DeleteVariationMenuItem = ( {
|
||||||
|
|
||||||
const variationId = useEntityId( 'postType', 'product_variation' );
|
const variationId = useEntityId( 'postType', 'product_variation' );
|
||||||
|
|
||||||
|
const { invalidateVariationList, goToNextVariation, numberOfVariations } =
|
||||||
|
useVariationSwitcher( {
|
||||||
|
parentId: productId ? parseInt( productId, 10 ) : undefined,
|
||||||
|
variationId,
|
||||||
|
} );
|
||||||
|
|
||||||
const [ name ] = useEntityProp< string >(
|
const [ name ] = useEntityProp< string >(
|
||||||
'postType',
|
'postType',
|
||||||
'product_variation',
|
'product_variation',
|
||||||
|
@ -82,9 +91,14 @@ export const DeleteVariationMenuItem = ( {
|
||||||
setShowModal( false );
|
setShowModal( false );
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
|
invalidateVariationList();
|
||||||
|
if ( numberOfVariations && numberOfVariations > 1 ) {
|
||||||
|
goToNextVariation();
|
||||||
|
} else {
|
||||||
navigateTo( {
|
navigateTo( {
|
||||||
url: getNewPath( {}, `/product/${ productId }` ),
|
url: getNewPath( {}, `/product/${ productId }` ),
|
||||||
} );
|
} );
|
||||||
|
}
|
||||||
} )
|
} )
|
||||||
.catch( () => {
|
.catch( () => {
|
||||||
createErrorNotice(
|
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