rebase code on oct-10-2023
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
ValidatedTextInput,
|
||||
isPostcode,
|
||||
type ValidatedTextInputHandle,
|
||||
} from '@woocommerce/blocks-checkout';
|
||||
import { ValidatedTextInput, isPostcode } from '@woocommerce/blocks-checkout';
|
||||
import {
|
||||
BillingCountryInput,
|
||||
ShippingCountryInput,
|
||||
@@ -14,110 +10,195 @@ import {
|
||||
BillingStateInput,
|
||||
ShippingStateInput,
|
||||
} from '@woocommerce/base-components/state-input';
|
||||
import { useEffect, useMemo, useRef } from '@wordpress/element';
|
||||
import { useEffect, useMemo } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { useShallowEqual } from '@woocommerce/base-hooks';
|
||||
import { defaultAddressFields } from '@woocommerce/settings';
|
||||
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||
import {
|
||||
AddressField,
|
||||
AddressFields,
|
||||
AddressType,
|
||||
defaultAddressFields,
|
||||
ShippingAddress,
|
||||
} from '@woocommerce/settings';
|
||||
import { useSelect, useDispatch, dispatch } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { FieldValidationStatus } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
AddressFormProps,
|
||||
FieldType,
|
||||
FieldConfig,
|
||||
AddressFormFields,
|
||||
} from './types';
|
||||
import prepareAddressFields from './prepare-address-fields';
|
||||
import validateShippingCountry from './validate-shipping-country';
|
||||
import customValidationHandler from './custom-validation-handler';
|
||||
|
||||
const defaultFields = Object.keys(
|
||||
defaultAddressFields
|
||||
) as unknown as FieldType[];
|
||||
// If it's the shipping address form and the user starts entering address
|
||||
// values without having set the country first, show an error.
|
||||
const validateShippingCountry = (
|
||||
values: ShippingAddress,
|
||||
setValidationErrors: (
|
||||
errors: Record< string, FieldValidationStatus >
|
||||
) => void,
|
||||
clearValidationError: ( error: string ) => void,
|
||||
hasValidationError: boolean
|
||||
): void => {
|
||||
const validationErrorId = 'shipping_country';
|
||||
if (
|
||||
! hasValidationError &&
|
||||
! values.country &&
|
||||
( values.city || values.state || values.postcode )
|
||||
) {
|
||||
setValidationErrors( {
|
||||
[ validationErrorId ]: {
|
||||
message: __(
|
||||
'Please select a country to calculate rates.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
}
|
||||
if ( hasValidationError && values.country ) {
|
||||
clearValidationError( validationErrorId );
|
||||
}
|
||||
};
|
||||
|
||||
interface AddressFormProps {
|
||||
// Id for component.
|
||||
id?: string;
|
||||
// Unique id for form.
|
||||
instanceId: string;
|
||||
// Array of fields in form.
|
||||
fields: ( keyof AddressFields )[];
|
||||
// Field configuration for fields in form.
|
||||
fieldConfig?: Record< keyof AddressFields, Partial< AddressField > >;
|
||||
// Function to all for an form onChange event.
|
||||
onChange: ( newValue: ShippingAddress ) => void;
|
||||
// Type of form.
|
||||
type?: AddressType;
|
||||
// Values for fields.
|
||||
values: ShippingAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout address form.
|
||||
*/
|
||||
const AddressForm = ( {
|
||||
id = '',
|
||||
fields = defaultFields,
|
||||
fieldConfig = {} as FieldConfig,
|
||||
fields = Object.keys(
|
||||
defaultAddressFields
|
||||
) as unknown as ( keyof AddressFields )[],
|
||||
fieldConfig = {} as Record< keyof AddressFields, Partial< AddressField > >,
|
||||
instanceId,
|
||||
onChange,
|
||||
type = 'shipping',
|
||||
values,
|
||||
}: AddressFormProps ): JSX.Element => {
|
||||
// Track incoming props.
|
||||
const validationErrorId = 'shipping_country';
|
||||
const { setValidationErrors, clearValidationError } =
|
||||
useDispatch( VALIDATION_STORE_KEY );
|
||||
|
||||
const countryValidationError = useSelect( ( select ) => {
|
||||
const store = select( VALIDATION_STORE_KEY );
|
||||
return store.getValidationError( validationErrorId );
|
||||
} );
|
||||
|
||||
const currentFields = useShallowEqual( fields );
|
||||
const currentFieldConfig = useShallowEqual( fieldConfig );
|
||||
const currentCountry = useShallowEqual( values.country );
|
||||
|
||||
// Memoize the address form fields passed in from the parent component.
|
||||
const addressFormFields = useMemo( (): AddressFormFields => {
|
||||
const preparedFields = prepareAddressFields(
|
||||
const addressFormFields = useMemo( () => {
|
||||
return prepareAddressFields(
|
||||
currentFields,
|
||||
currentFieldConfig,
|
||||
currentCountry
|
||||
fieldConfig,
|
||||
values.country
|
||||
);
|
||||
return {
|
||||
fields: preparedFields,
|
||||
type,
|
||||
required: preparedFields.filter( ( field ) => field.required ),
|
||||
hidden: preparedFields.filter( ( field ) => field.hidden ),
|
||||
};
|
||||
}, [ currentFields, currentFieldConfig, currentCountry, type ] );
|
||||
|
||||
// Stores refs for rendered fields so we can access them later.
|
||||
const fieldsRef = useRef<
|
||||
Record< string, ValidatedTextInputHandle | null >
|
||||
>( {} );
|
||||
}, [ currentFields, fieldConfig, values.country ] );
|
||||
|
||||
// Clear values for hidden fields.
|
||||
useEffect( () => {
|
||||
const newValues = {
|
||||
...values,
|
||||
...Object.fromEntries(
|
||||
addressFormFields.hidden.map( ( field ) => [ field.key, '' ] )
|
||||
),
|
||||
};
|
||||
if ( ! isShallowEqual( values, newValues ) ) {
|
||||
onChange( newValues );
|
||||
}
|
||||
}, [ onChange, addressFormFields, values ] );
|
||||
addressFormFields.forEach( ( field ) => {
|
||||
if ( field.hidden && values[ field.key ] ) {
|
||||
onChange( {
|
||||
...values,
|
||||
[ field.key ]: '',
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}, [ addressFormFields, onChange, values ] );
|
||||
|
||||
// Clear postcode validation error if postcode is not required.
|
||||
useEffect( () => {
|
||||
addressFormFields.forEach( ( field ) => {
|
||||
if ( field.key === 'postcode' && field.required === false ) {
|
||||
const store = dispatch( 'wc/store/validation' );
|
||||
|
||||
if ( type === 'shipping' ) {
|
||||
store.clearValidationError( 'shipping_postcode' );
|
||||
}
|
||||
|
||||
if ( type === 'billing' ) {
|
||||
store.clearValidationError( 'billing_postcode' );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}, [ addressFormFields, type, clearValidationError ] );
|
||||
|
||||
// Maybe validate country when other fields change so user is notified that it's required.
|
||||
useEffect( () => {
|
||||
if ( type === 'shipping' ) {
|
||||
validateShippingCountry( values );
|
||||
validateShippingCountry(
|
||||
values,
|
||||
setValidationErrors,
|
||||
clearValidationError,
|
||||
!! countryValidationError?.message &&
|
||||
! countryValidationError?.hidden
|
||||
);
|
||||
}
|
||||
}, [ values, type ] );
|
||||
|
||||
// Changing country may change format for postcodes.
|
||||
useEffect( () => {
|
||||
fieldsRef.current?.postcode?.revalidate();
|
||||
}, [ currentCountry ] );
|
||||
}, [
|
||||
values,
|
||||
countryValidationError?.message,
|
||||
countryValidationError?.hidden,
|
||||
setValidationErrors,
|
||||
clearValidationError,
|
||||
type,
|
||||
] );
|
||||
|
||||
id = id || instanceId;
|
||||
|
||||
/**
|
||||
* Custom validation handler for fields with field specific handling.
|
||||
*/
|
||||
const customValidationHandler = (
|
||||
inputObject: HTMLInputElement,
|
||||
field: string,
|
||||
customValues: {
|
||||
country: string;
|
||||
}
|
||||
): boolean => {
|
||||
if (
|
||||
field === 'postcode' &&
|
||||
customValues.country &&
|
||||
! isPostcode( {
|
||||
postcode: inputObject.value,
|
||||
country: customValues.country,
|
||||
} )
|
||||
) {
|
||||
inputObject.setCustomValidity(
|
||||
__(
|
||||
'Please enter a valid postcode',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={ id } className="wc-block-components-address-form">
|
||||
{ addressFormFields.fields.map( ( field ) => {
|
||||
{ addressFormFields.map( ( field ) => {
|
||||
if ( field.hidden ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldProps = {
|
||||
id: `${ id }-${ field.key }`,
|
||||
errorId: `${ type }_${ field.key }`,
|
||||
label: field.required ? field.label : field.optionalLabel,
|
||||
autoCapitalize: field.autocapitalize,
|
||||
autoComplete: field.autocomplete,
|
||||
errorMessage: field.errorMessage,
|
||||
required: field.required,
|
||||
className: `wc-block-components-address-form__${ field.key }`,
|
||||
};
|
||||
// Create a consistent error ID based on the field key and type
|
||||
const errorId = `${ type }_${ field.key }`;
|
||||
|
||||
if ( field.key === 'country' ) {
|
||||
const Tag =
|
||||
@@ -127,26 +208,24 @@ const AddressForm = ( {
|
||||
return (
|
||||
<Tag
|
||||
key={ field.key }
|
||||
{ ...fieldProps }
|
||||
id={ `${ id }-${ field.key }` }
|
||||
errorId={ errorId }
|
||||
label={
|
||||
field.required
|
||||
? field.label
|
||||
: field.optionalLabel
|
||||
}
|
||||
value={ values.country }
|
||||
onChange={ ( newCountry ) => {
|
||||
const newValues = {
|
||||
autoComplete={ field.autocomplete }
|
||||
onChange={ ( newValue ) =>
|
||||
onChange( {
|
||||
...values,
|
||||
country: newCountry,
|
||||
country: newValue,
|
||||
state: '',
|
||||
};
|
||||
// Country will impact postcode too. Do we need to clear it?
|
||||
if (
|
||||
values.postcode &&
|
||||
! isPostcode( {
|
||||
postcode: values.postcode,
|
||||
country: newCountry,
|
||||
} )
|
||||
) {
|
||||
newValues.postcode = '';
|
||||
}
|
||||
onChange( newValues );
|
||||
} }
|
||||
} )
|
||||
}
|
||||
errorMessage={ field.errorMessage }
|
||||
required={ field.required }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -159,15 +238,24 @@ const AddressForm = ( {
|
||||
return (
|
||||
<Tag
|
||||
key={ field.key }
|
||||
{ ...fieldProps }
|
||||
id={ `${ id }-${ field.key }` }
|
||||
errorId={ errorId }
|
||||
country={ values.country }
|
||||
label={
|
||||
field.required
|
||||
? field.label
|
||||
: field.optionalLabel
|
||||
}
|
||||
value={ values.state }
|
||||
autoComplete={ field.autocomplete }
|
||||
onChange={ ( newValue ) =>
|
||||
onChange( {
|
||||
...values,
|
||||
state: newValue,
|
||||
} )
|
||||
}
|
||||
errorMessage={ field.errorMessage }
|
||||
required={ field.required }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -175,30 +263,35 @@ const AddressForm = ( {
|
||||
return (
|
||||
<ValidatedTextInput
|
||||
key={ field.key }
|
||||
ref={ ( el ) =>
|
||||
( fieldsRef.current[ field.key ] = el )
|
||||
id={ `${ id }-${ field.key }` }
|
||||
errorId={ errorId }
|
||||
className={ `wc-block-components-address-form__${ field.key }` }
|
||||
label={
|
||||
field.required ? field.label : field.optionalLabel
|
||||
}
|
||||
{ ...fieldProps }
|
||||
value={ values[ field.key ] }
|
||||
autoCapitalize={ field.autocapitalize }
|
||||
autoComplete={ field.autocomplete }
|
||||
onChange={ ( newValue: string ) =>
|
||||
onChange( {
|
||||
...values,
|
||||
[ field.key ]: newValue,
|
||||
[ field.key ]:
|
||||
field.key === 'postcode'
|
||||
? newValue.trimStart().toUpperCase()
|
||||
: newValue,
|
||||
} )
|
||||
}
|
||||
customFormatter={ ( value: string ) => {
|
||||
if ( field.key === 'postcode' ) {
|
||||
return value.trimStart().toUpperCase();
|
||||
}
|
||||
return value;
|
||||
} }
|
||||
customValidation={ ( inputObject: HTMLInputElement ) =>
|
||||
customValidationHandler(
|
||||
inputObject,
|
||||
field.key,
|
||||
values
|
||||
)
|
||||
field.required || inputObject.value
|
||||
? customValidationHandler(
|
||||
inputObject,
|
||||
field.key,
|
||||
values
|
||||
)
|
||||
: true
|
||||
}
|
||||
errorMessage={ field.errorMessage }
|
||||
required={ field.required }
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { isPostcode } from '@woocommerce/blocks-checkout';
|
||||
|
||||
/**
|
||||
* Custom validation handler for fields with field specific handling.
|
||||
*/
|
||||
const customValidationHandler = (
|
||||
inputObject: HTMLInputElement,
|
||||
field: string,
|
||||
customValues: {
|
||||
country: string;
|
||||
}
|
||||
): boolean => {
|
||||
// Pass validation if the field is not required and is empty.
|
||||
if ( ! inputObject.required && ! inputObject.value ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
field === 'postcode' &&
|
||||
customValues.country &&
|
||||
! isPostcode( {
|
||||
postcode: inputObject.value,
|
||||
country: customValues.country,
|
||||
} )
|
||||
) {
|
||||
inputObject.setCustomValidity(
|
||||
__(
|
||||
'Please enter a valid postcode',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export default customValidationHandler;
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type {
|
||||
AddressField,
|
||||
AddressFields,
|
||||
AddressType,
|
||||
ShippingAddress,
|
||||
KeyedAddressField,
|
||||
} from '@woocommerce/settings';
|
||||
|
||||
export type FieldConfig = Record<
|
||||
keyof AddressFields,
|
||||
Partial< AddressField >
|
||||
>;
|
||||
|
||||
export type FieldType = keyof AddressFields;
|
||||
|
||||
export type AddressFormFields = {
|
||||
fields: KeyedAddressField[];
|
||||
type: AddressType;
|
||||
required: KeyedAddressField[];
|
||||
hidden: KeyedAddressField[];
|
||||
};
|
||||
|
||||
export interface AddressFormProps {
|
||||
// Id for component.
|
||||
id?: string;
|
||||
// Unique id for form.
|
||||
instanceId: string;
|
||||
// Type of form (billing or shipping).
|
||||
type?: AddressType;
|
||||
// Array of fields in form.
|
||||
fields: FieldType[];
|
||||
// Field configuration for fields in form.
|
||||
fieldConfig?: FieldConfig;
|
||||
// Called with the new address data when the address form changes. This is only called when all required fields are filled and there are no validation errors.
|
||||
onChange: ( newValue: ShippingAddress ) => void;
|
||||
// Values for fields.
|
||||
values: ShippingAddress;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { type ShippingAddress } from '@woocommerce/settings';
|
||||
import { select, dispatch } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
// If it's the shipping address form and the user starts entering address
|
||||
// values without having set the country first, show an error.
|
||||
const validateShippingCountry = ( values: ShippingAddress ): void => {
|
||||
const validationErrorId = 'shipping_country';
|
||||
const hasValidationError =
|
||||
select( VALIDATION_STORE_KEY ).getValidationError( validationErrorId );
|
||||
if (
|
||||
! values.country &&
|
||||
( values.city || values.state || values.postcode )
|
||||
) {
|
||||
if ( hasValidationError ) {
|
||||
dispatch( VALIDATION_STORE_KEY ).showValidationError(
|
||||
validationErrorId
|
||||
);
|
||||
} else {
|
||||
dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
|
||||
[ validationErrorId ]: {
|
||||
message: __(
|
||||
'Please select your country',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
if ( hasValidationError && values.country ) {
|
||||
dispatch( VALIDATION_STORE_KEY ).clearValidationError(
|
||||
validationErrorId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default validateShippingCountry;
|
||||
@@ -7,8 +7,12 @@ import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { Panel } from '@woocommerce/blocks-checkout';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { useShippingData } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
useShippingData,
|
||||
useStoreEvents,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { sanitizeHTML } from '@woocommerce/utils';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
/**
|
||||
@@ -27,7 +31,8 @@ export const ShippingRatesControlPackage = ( {
|
||||
collapsible,
|
||||
showItems,
|
||||
}: PackageProps ): ReactElement => {
|
||||
const { selectShippingRate, isSelectingRate } = useShippingData();
|
||||
const { selectShippingRate } = useShippingData();
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const multiplePackages =
|
||||
document.querySelectorAll(
|
||||
'.wc-block-components-shipping-rates-control__package'
|
||||
@@ -90,32 +95,28 @@ export const ShippingRatesControlPackage = ( {
|
||||
const onSelectRate = useCallback(
|
||||
( newShippingRateId: string ) => {
|
||||
selectShippingRate( newShippingRateId, packageId );
|
||||
dispatchCheckoutEvent( 'set-selected-shipping-rate', {
|
||||
shippingRateId: newShippingRateId,
|
||||
} );
|
||||
},
|
||||
[ packageId, selectShippingRate ]
|
||||
[ dispatchCheckoutEvent, packageId, selectShippingRate ]
|
||||
);
|
||||
const debouncedOnSelectRate = useDebouncedCallback( onSelectRate, 1000 );
|
||||
const packageRatesProps = {
|
||||
className,
|
||||
noResultsMessage,
|
||||
rates: packageData.shipping_rates,
|
||||
onSelectRate,
|
||||
onSelectRate: debouncedOnSelectRate,
|
||||
selectedRate: packageData.shipping_rates.find(
|
||||
( rate ) => rate.selected
|
||||
),
|
||||
renderOption,
|
||||
disabled: isSelectingRate,
|
||||
};
|
||||
|
||||
if ( shouldBeCollapsible ) {
|
||||
return (
|
||||
<Panel
|
||||
className={ classNames(
|
||||
'wc-block-components-shipping-rates-control__package',
|
||||
className,
|
||||
{
|
||||
'wc-block-components-shipping-rates-control__package--disabled':
|
||||
isSelectingRate,
|
||||
}
|
||||
) }
|
||||
className="wc-block-components-shipping-rates-control__package"
|
||||
// initialOpen remembers only the first value provided to it, so by the
|
||||
// time we know we have several packages, initialOpen would be hardcoded to true.
|
||||
// If we're rendering a panel, we're more likely rendering several
|
||||
@@ -132,11 +133,7 @@ export const ShippingRatesControlPackage = ( {
|
||||
<div
|
||||
className={ classNames(
|
||||
'wc-block-components-shipping-rates-control__package',
|
||||
className,
|
||||
{
|
||||
'wc-block-components-shipping-rates-control__package--disabled':
|
||||
isSelectingRate,
|
||||
}
|
||||
className
|
||||
) }
|
||||
>
|
||||
{ header }
|
||||
|
||||
@@ -6,7 +6,6 @@ import RadioControl, {
|
||||
RadioControlOptionLayout,
|
||||
} from '@woocommerce/base-components/radio-control';
|
||||
import type { CartShippingPackageShippingRate } from '@woocommerce/types';
|
||||
import { usePrevious } from '@woocommerce/base-hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -21,7 +20,6 @@ interface PackageRates {
|
||||
className?: string;
|
||||
noResultsMessage: JSX.Element;
|
||||
selectedRate: CartShippingPackageShippingRate | undefined;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const PackageRates = ( {
|
||||
@@ -31,37 +29,34 @@ const PackageRates = ( {
|
||||
rates,
|
||||
renderOption = renderPackageRateOption,
|
||||
selectedRate,
|
||||
disabled = false,
|
||||
}: PackageRates ): JSX.Element => {
|
||||
const selectedRateId = selectedRate?.rate_id || '';
|
||||
const previousSelectedRateId = usePrevious( selectedRateId );
|
||||
|
||||
// Store selected rate ID in local state so shipping rates changes are shown in the UI instantly.
|
||||
const [ selectedOption, setSelectedOption ] = useState( () => {
|
||||
if ( selectedRateId ) {
|
||||
return selectedRateId;
|
||||
}
|
||||
// Default to first rate if no rate is selected.
|
||||
return rates[ 0 ]?.rate_id;
|
||||
} );
|
||||
const [ selectedOption, setSelectedOption ] = useState( selectedRateId );
|
||||
|
||||
// Update the selected option if cart state changes in the data store.
|
||||
// Update the selected option if cart state changes in the data stores.
|
||||
useEffect( () => {
|
||||
if (
|
||||
selectedRateId &&
|
||||
selectedRateId !== previousSelectedRateId &&
|
||||
selectedRateId !== selectedOption
|
||||
) {
|
||||
if ( selectedRateId ) {
|
||||
setSelectedOption( selectedRateId );
|
||||
}
|
||||
}, [ selectedRateId, selectedOption, previousSelectedRateId ] );
|
||||
}, [ selectedRateId ] );
|
||||
|
||||
// Update the data store when the local selected rate changes.
|
||||
// Update the selected option if there is no rate selected on mount.
|
||||
useEffect( () => {
|
||||
if ( selectedOption ) {
|
||||
onSelectRate( selectedOption );
|
||||
// Check the rates to see if any are marked as selected. At least one should be. If no rate is selected, it could be
|
||||
// that the user toggled quickly from local pickup back to shipping.
|
||||
const isRateSelectedInDataStore = rates.some(
|
||||
( { selected } ) => selected
|
||||
);
|
||||
if (
|
||||
( ! selectedOption && rates[ 0 ] ) ||
|
||||
! isRateSelectedInDataStore
|
||||
) {
|
||||
setSelectedOption( rates[ 0 ]?.rate_id );
|
||||
onSelectRate( rates[ 0 ]?.rate_id );
|
||||
}
|
||||
}, [ onSelectRate, selectedOption ] );
|
||||
}, [ onSelectRate, rates, selectedOption ] );
|
||||
|
||||
if ( rates.length === 0 ) {
|
||||
return noResultsMessage;
|
||||
@@ -75,7 +70,6 @@ const PackageRates = ( {
|
||||
setSelectedOption( value );
|
||||
onSelectRate( value );
|
||||
} }
|
||||
disabled={ disabled }
|
||||
selected={ selectedOption }
|
||||
options={ rates.map( renderOption ) }
|
||||
/>
|
||||
|
||||
@@ -44,11 +44,6 @@
|
||||
.wc-block-components-radio-control__description-group {
|
||||
@include font-size(smaller);
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.5;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-shipping-rates-control__package-items {
|
||||
|
||||
@@ -61,7 +61,6 @@ jest.mock( '@woocommerce/base-context/hooks', () => {
|
||||
} );
|
||||
baseContextHooks.useShippingData.mockReturnValue( {
|
||||
needsShipping: true,
|
||||
selectShippingRate: jest.fn(),
|
||||
shippingRates: [
|
||||
{
|
||||
package_id: 0,
|
||||
|
||||
@@ -24,7 +24,7 @@ export const CountryInput = ( {
|
||||
required = false,
|
||||
errorId,
|
||||
errorMessage = __(
|
||||
'Please select a country',
|
||||
'Please select a country.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
}: CountryInputWithCountriesProps ): JSX.Element => {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
// Legacy notice compatibility.
|
||||
.wc-forward {
|
||||
.wc-forward.wp-element-button {
|
||||
float: right;
|
||||
color: $gray-800 !important;
|
||||
background: transparent;
|
||||
@@ -52,8 +52,6 @@
|
||||
border: 0;
|
||||
appearance: none;
|
||||
opacity: 0.6;
|
||||
text-decoration-line: underline;
|
||||
text-underline-position: under;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
width: 100%;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
outline: none !important;
|
||||
position: absolute;
|
||||
@@ -365,6 +366,7 @@
|
||||
@include ie11() {
|
||||
.wc-block-components-price-slider__range-input-wrapper {
|
||||
border: 0;
|
||||
height: auto;
|
||||
position: relative;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,9 @@
|
||||
}
|
||||
|
||||
&__container {
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&__stars + &__reviews_count {
|
||||
margin-left: $gap-smaller;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: $gap-smaller;
|
||||
}
|
||||
|
||||
&__norating-container {
|
||||
|
||||
@@ -16,7 +16,6 @@ const RadioControl = ( {
|
||||
selected = '',
|
||||
onChange,
|
||||
options = [],
|
||||
disabled = false,
|
||||
}: RadioControlProps ): JSX.Element | null => {
|
||||
const instanceId = useInstanceId( RadioControl );
|
||||
const radioControlId = id || instanceId;
|
||||
@@ -44,7 +43,6 @@ const RadioControl = ( {
|
||||
option.onChange( value );
|
||||
}
|
||||
} }
|
||||
disabled={ disabled }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,6 @@ const Option = ( {
|
||||
name,
|
||||
onChange,
|
||||
option,
|
||||
disabled = false,
|
||||
}: RadioControlOptionProps ): JSX.Element => {
|
||||
const { value, label, description, secondaryLabel, secondaryDescription } =
|
||||
option;
|
||||
@@ -47,7 +46,6 @@ const Option = ( {
|
||||
[ `${ name }-${ value }__secondary-description` ]:
|
||||
secondaryDescription,
|
||||
} ) }
|
||||
disabled={ disabled }
|
||||
/>
|
||||
<OptionLayout
|
||||
id={ `${ name }-${ value }` }
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
@include reset-typography();
|
||||
display: block;
|
||||
margin: em($gap) 0;
|
||||
margin-top: 0;
|
||||
padding: 0 0 0 em($gap-larger);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -97,12 +99,6 @@
|
||||
background: $input-text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ export interface RadioControlProps {
|
||||
onChange: ( value: string ) => void;
|
||||
// List of radio control options.
|
||||
options: RadioControlOption[];
|
||||
// Is the control disabled.
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface RadioControlOptionProps {
|
||||
@@ -23,7 +21,6 @@ export interface RadioControlOptionProps {
|
||||
name?: string;
|
||||
onChange: ( value: string ) => void;
|
||||
option: RadioControlOption;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface RadioControlOptionContent {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
max-width: 600px;
|
||||
margin: 0;
|
||||
pointer-events: all;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@@ -55,15 +55,13 @@ const StateInput = ( {
|
||||
*/
|
||||
const onChangeState = useCallback(
|
||||
( stateValue: string ) => {
|
||||
const newValue =
|
||||
onChange(
|
||||
options.length > 0
|
||||
? optionMatcher( stateValue, options )
|
||||
: stateValue;
|
||||
if ( newValue !== value ) {
|
||||
onChange( newValue );
|
||||
}
|
||||
: stateValue
|
||||
);
|
||||
},
|
||||
[ onChange, options, value ]
|
||||
[ onChange, options ]
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,7 +124,11 @@ export const useShippingData = (): ShippingData => {
|
||||
processErrorResponse( error );
|
||||
} );
|
||||
},
|
||||
[ dispatchSelectShippingRate, dispatchCheckoutEvent ]
|
||||
[
|
||||
hasSelectedLocalPickup,
|
||||
dispatchSelectShippingRate,
|
||||
dispatchCheckoutEvent,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { doAction } from '@wordpress/hooks';
|
||||
import { select } from '@wordpress/data';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { useCallback, useRef, useEffect } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useStoreCart } from './cart/use-store-cart';
|
||||
|
||||
type StoreEvent = (
|
||||
eventName: string,
|
||||
@@ -17,6 +21,15 @@ export const useStoreEvents = (): {
|
||||
dispatchStoreEvent: StoreEvent;
|
||||
dispatchCheckoutEvent: StoreEvent;
|
||||
} => {
|
||||
const storeCart = useStoreCart();
|
||||
const currentStoreCart = useRef( storeCart );
|
||||
|
||||
// Track the latest version of the cart so we can use the current value in our callback function below without triggering
|
||||
// other useEffect hooks using dispatchCheckoutEvent as a dependency.
|
||||
useEffect( () => {
|
||||
currentStoreCart.current = storeCart;
|
||||
}, [ storeCart ] );
|
||||
|
||||
const dispatchStoreEvent = useCallback( ( eventName, eventParams = {} ) => {
|
||||
try {
|
||||
doAction(
|
||||
@@ -37,7 +50,7 @@ export const useStoreEvents = (): {
|
||||
`experimental__woocommerce_blocks-checkout-${ eventName }`,
|
||||
{
|
||||
...eventParams,
|
||||
storeCart: select( 'wc/store/cart' ).getCartData(),
|
||||
storeCart: currentStoreCart.current,
|
||||
}
|
||||
);
|
||||
} catch ( e ) {
|
||||
|
||||
@@ -50,8 +50,8 @@ function getBorderClassName( attributes: {
|
||||
: '';
|
||||
|
||||
return classnames( {
|
||||
'has-border-color': !! borderColor || !! style?.border?.color,
|
||||
[ borderColorClass ]: !! borderColorClass,
|
||||
'has-border-color': borderColor || style?.border?.color,
|
||||
borderColorClass,
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,4 @@ export * from './camel-case-keys';
|
||||
export * from './snake-case-keys';
|
||||
export * from './debounce';
|
||||
export * from './keyby';
|
||||
export * from './pick';
|
||||
export * from './get-inline-styles';
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Creates an object composed of the picked object properties.
|
||||
*/
|
||||
export const pick = < Type >( object: Type, keys: string[] ): Type => {
|
||||
return keys.reduce( ( obj, key ) => {
|
||||
if ( object && object.hasOwnProperty( key ) ) {
|
||||
obj[ key as keyof Type ] = object[ key as keyof Type ];
|
||||
}
|
||||
return obj;
|
||||
}, {} as Type );
|
||||
};
|
||||
Reference in New Issue
Block a user