woocommerce/plugins/woocommerce-blocks/packages/components/text-input/validated-text-input.tsx

285 lines
6.6 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import {
useEffect,
useState,
useCallback,
forwardRef,
useImperativeHandle,
useRef,
} from '@wordpress/element';
import clsx from 'clsx';
import { isObject } from '@woocommerce/types';
import { useDispatch, useSelect } from '@wordpress/data';
Convert validation context to data store (https://github.com/woocommerce/woocommerce-blocks/pull/6402) * Add validation reducers, actions, and action types * Add selector for getValidationErrors * Export store key and register store * Export validation store key * Move TextInput files to checkout package * Export ValidatedTextInput from blocks-checkout package * Update imports of ValidatedTextInput to reflect new location * Use the validation wp-data store for showing error messages * Export getValidationError in checkout package * Move validation store to checkout package * Move ValidationInputError to blocks-checkout package * Only export "exposedSelectors" from validation * Convert validation context to data store * Fixed linting error * Fixed linting error * Change the validation selectors to return a function * Convert reducer and selectors to TS * Remove superfluous comments and improve test titles * Test to ensure visible errors remain visible * Make test for hasValidationErrors more robust * Augment the wp-data module to include our selectors and actions * Removed unused `exposedSelectors` variable * Remove TS error because of `instanceId` on props * Remove unnecessary as const * Use function returned by getValidationError * Use correct selector/action names now context has been decoupled * hide validation error when input value changes * Add correct aria-describedBy now we can get error id from store * Clear validation error from store when component unmounts * Clear validation error if input is valid * convert ValidationInputError to TS and get correct id/error from store * Ensure checkout block doesn't break when there are no errors * Get validation data from the store instead of context * Update country input to remove validation context * Move validation store out of checkout package * Move TextInput and ValidationInputError back out of the checkout package * Remove duplicate internal styles comment * Remove exports that no longer exist * Get validation store key from block-data * Make attribute-select-control use validation data store * Export FieldValidationStatus type * Make combobox use validation store not context * Make Address use validation store not context * Make Address use validation store not context * Use hasValidationErrors selector as a function in shipping calculator * Remove validation context from coupon story * Import VALIDATION_STORE_KEY from correct location * Stop coupon story from erroring * Update useStoreCartCoupons to use validation store not context * Make TotalsCoupon use validation store instead of context * Make AddToCartFormContext use validation store not context * Remove ValidationContext * Import FieldValidationStatus from correct location * Import ValidatedTextInput and ValidatedTextInput from correct location * Remove ValidationContextProvider * Update components to use validation store not context * Update useValidation to use the data store * Replace the validation context in checkout-events file * Use the re-mapped path for the store key import * Use "register" instead of the deprecated "registerStore" * Fix import error of the "FieldValidationStatus" type * Use TS instead of React's "PropTypes" * Fix the type of "ValidationInputError" in the "payment-method-interface" * Fix error not showing on the first place order click bug We were mutating the state in the reducer, which prevented re-rendering on state change * Fix state mutation issue in the Validation reducer Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Saad Tarhi <saad.trh@gmail.com>
2022-07-01 23:06:25 +00:00
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { usePrevious } from '@woocommerce/base-hooks';
import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import TextInput from './text-input';
import './style.scss';
Convert validation context to data store (https://github.com/woocommerce/woocommerce-blocks/pull/6402) * Add validation reducers, actions, and action types * Add selector for getValidationErrors * Export store key and register store * Export validation store key * Move TextInput files to checkout package * Export ValidatedTextInput from blocks-checkout package * Update imports of ValidatedTextInput to reflect new location * Use the validation wp-data store for showing error messages * Export getValidationError in checkout package * Move validation store to checkout package * Move ValidationInputError to blocks-checkout package * Only export "exposedSelectors" from validation * Convert validation context to data store * Fixed linting error * Fixed linting error * Change the validation selectors to return a function * Convert reducer and selectors to TS * Remove superfluous comments and improve test titles * Test to ensure visible errors remain visible * Make test for hasValidationErrors more robust * Augment the wp-data module to include our selectors and actions * Removed unused `exposedSelectors` variable * Remove TS error because of `instanceId` on props * Remove unnecessary as const * Use function returned by getValidationError * Use correct selector/action names now context has been decoupled * hide validation error when input value changes * Add correct aria-describedBy now we can get error id from store * Clear validation error from store when component unmounts * Clear validation error if input is valid * convert ValidationInputError to TS and get correct id/error from store * Ensure checkout block doesn't break when there are no errors * Get validation data from the store instead of context * Update country input to remove validation context * Move validation store out of checkout package * Move TextInput and ValidationInputError back out of the checkout package * Remove duplicate internal styles comment * Remove exports that no longer exist * Get validation store key from block-data * Make attribute-select-control use validation data store * Export FieldValidationStatus type * Make combobox use validation store not context * Make Address use validation store not context * Make Address use validation store not context * Use hasValidationErrors selector as a function in shipping calculator * Remove validation context from coupon story * Import VALIDATION_STORE_KEY from correct location * Stop coupon story from erroring * Update useStoreCartCoupons to use validation store not context * Make TotalsCoupon use validation store instead of context * Make AddToCartFormContext use validation store not context * Remove ValidationContext * Import FieldValidationStatus from correct location * Import ValidatedTextInput and ValidatedTextInput from correct location * Remove ValidationContextProvider * Update components to use validation store not context * Update useValidation to use the data store * Replace the validation context in checkout-events file * Use the re-mapped path for the store key import * Use "register" instead of the deprecated "registerStore" * Fix import error of the "FieldValidationStatus" type * Use TS instead of React's "PropTypes" * Fix the type of "ValidationInputError" in the "payment-method-interface" * Fix error not showing on the first place order click bug We were mutating the state in the reducer, which prevented re-rendering on state change * Fix state mutation issue in the Validation reducer Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Saad Tarhi <saad.trh@gmail.com>
2022-07-01 23:06:25 +00:00
import { ValidationInputError } from '../validation-input-error';
import { getValidityMessageForInput } from '../../checkout/utils';
import { ValidatedTextInputProps } from './types';
export type ValidatedTextInputHandle = {
[Accessibility] Focus coupon input if it has an error (#48738) * Focus cupon input if it has errors * Add changelog file * Add styles to coupon field with error on cart page * Make coupon errors accessible on the cart page * Add styles to coupon field with error on checkout page * Rename coupon variables on cart * Focus coupon field with error before updating live region on cart page * Focus coupon field with error before updating live region on checkout page * Remove incorrect early return * Update coupon error notice test * Improve coupon error message semantics * Fix coupon errors for block based themes * Update tests to not look for coupon errors on the notice block * Rename coupon-error-message to coupon-error-notice * Fix notice if coupon doesn't exist on tests * FIx invalid coupon notice on classic theme test * Update test for coupon inline notice * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Don't clear coupon input if code doesn't exist * Create coupon error notice mixin * Update coupon error notice styles * Coupon error notice T19 compatibility * Coupon error notice T17 compatibility * Coupon error notice TT1 compatibility * Coupon error notice TT compatibility * Coupon error notice TT2 compatibility * Coupon error notice TT3 compatibility * Add spaces around paramenters Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Add spaces around paramenters Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Replace $red SCSS variable with a CSS one * Keep input with the invalid coupon code after notice appears * Fix typo in comment Co-authored-by: Mike Jolley <mike.jolley@me.com> * Fix typo in comment Co-authored-by: Mike Jolley <mike.jolley@me.com> * Break notice message into two lines * Break if statement into two lines --------- Co-authored-by: Darin Kotter <darin.kotter@gmail.com> Co-authored-by: Mike Jolley <mike.jolley@me.com>
2024-08-23 18:22:28 +00:00
focus?: () => void;
revalidate: () => void;
};
/**
* A text based input which validates the input value.
*/
const ValidatedTextInput = forwardRef<
ValidatedTextInputHandle,
ValidatedTextInputProps
>(
(
{
className,
id,
type = 'text',
ariaDescribedBy,
errorId,
focusOnMount = false,
onChange,
showError = true,
errorMessage: passedErrorMessage = '',
value = '',
customValidation = () => true,
Checkout: Add password field to create account form (#48985) * Add site title to account checkbox * Add customer_password support to Store API * Hide password nag if defining own password * Add woocommerce_registration_generate_password option to block assets * Change login prompt to just "log in" * Add default styling to password inputs * Reset line height for checkbox inputs * Add customer password to store * Add password field to contact information block * Handle customer password in checkout processor * Styling for new elements * Update tests so they match new create account label * Update log in link in tests * Add e2e tests for password field * Add validation message and fix rendering when account is required * Changelog * Add missing api to tests * Remove console log * rerender checkout * Update log in link in test * Adjust validation so we can change the label in messages with custom callback * Use queryByText in test * Make sure password generation is on in tests * Create password if provided password is empty * Skip "Place order" button translation test * Revert "Skip "Place order" button translation test" This reverts commit 7aed6137e88cdb3577f74f6f0c05258b531ed534. * Update plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-contact-information-block/block.tsx Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Update plugins/woocommerce-blocks/assets/js/data/checkout/reducers.ts Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Comment empty condition * Update CSS classnames * Return null in CreateAccountUI if nothing to display * Linting: Return return param * Document $password param --------- Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2024-07-15 10:43:02 +00:00
customValidityMessage,
feedback = null,
customFormatter = ( newValue: string ) => newValue,
label,
validateOnMount = true,
instanceId: preferredInstanceId = '',
...rest
},
forwardedRef
): JSX.Element => {
// True on mount.
const [ isPristine, setIsPristine ] = useState( true );
// Track incoming value.
const previousValue = usePrevious( value );
// Ref for the input element.
const inputRef = useRef< HTMLInputElement >( null );
const instanceId = useInstanceId(
ValidatedTextInput,
'',
preferredInstanceId
);
const textInputId =
typeof id !== 'undefined' ? id : 'textinput-' + instanceId;
const errorIdString = errorId !== undefined ? errorId : textInputId;
const {
setValidationErrors,
hideValidationError,
clearValidationError,
} = useDispatch( VALIDATION_STORE_KEY );
// Ref for validation callback.
const customValidationRef = useRef( customValidation );
// Update ref when validation callback changes.
useEffect( () => {
customValidationRef.current = customValidation;
}, [ customValidation ] );
const { validationError, validationErrorId } = useSelect(
( select ) => {
const store = select( VALIDATION_STORE_KEY );
return {
validationError: store.getValidationError( errorIdString ),
validationErrorId:
store.getValidationErrorId( errorIdString ),
};
}
);
const validateInput = useCallback(
( errorsHidden = true ) => {
const inputObject = inputRef.current || null;
if ( inputObject === null ) {
return;
}
// Trim white space before validation.
inputObject.value = inputObject.value.trim();
inputObject.setCustomValidity( '' );
if (
inputObject.checkValidity() &&
customValidationRef.current( inputObject )
) {
clearValidationError( errorIdString );
return;
}
setValidationErrors( {
[ errorIdString ]: {
Checkout: Add password field to create account form (#48985) * Add site title to account checkbox * Add customer_password support to Store API * Hide password nag if defining own password * Add woocommerce_registration_generate_password option to block assets * Change login prompt to just "log in" * Add default styling to password inputs * Reset line height for checkbox inputs * Add customer password to store * Add password field to contact information block * Handle customer password in checkout processor * Styling for new elements * Update tests so they match new create account label * Update log in link in tests * Add e2e tests for password field * Add validation message and fix rendering when account is required * Changelog * Add missing api to tests * Remove console log * rerender checkout * Update log in link in test * Adjust validation so we can change the label in messages with custom callback * Use queryByText in test * Make sure password generation is on in tests * Create password if provided password is empty * Skip "Place order" button translation test * Revert "Skip "Place order" button translation test" This reverts commit 7aed6137e88cdb3577f74f6f0c05258b531ed534. * Update plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-contact-information-block/block.tsx Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Update plugins/woocommerce-blocks/assets/js/data/checkout/reducers.ts Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Comment empty condition * Update CSS classnames * Return null in CreateAccountUI if nothing to display * Linting: Return return param * Document $password param --------- Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2024-07-15 10:43:02 +00:00
message: getValidityMessageForInput(
label,
inputObject,
customValidityMessage
),
hidden: errorsHidden,
},
} );
},
Checkout: Add password field to create account form (#48985) * Add site title to account checkbox * Add customer_password support to Store API * Hide password nag if defining own password * Add woocommerce_registration_generate_password option to block assets * Change login prompt to just "log in" * Add default styling to password inputs * Reset line height for checkbox inputs * Add customer password to store * Add password field to contact information block * Handle customer password in checkout processor * Styling for new elements * Update tests so they match new create account label * Update log in link in tests * Add e2e tests for password field * Add validation message and fix rendering when account is required * Changelog * Add missing api to tests * Remove console log * rerender checkout * Update log in link in test * Adjust validation so we can change the label in messages with custom callback * Use queryByText in test * Make sure password generation is on in tests * Create password if provided password is empty * Skip "Place order" button translation test * Revert "Skip "Place order" button translation test" This reverts commit 7aed6137e88cdb3577f74f6f0c05258b531ed534. * Update plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-contact-information-block/block.tsx Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Update plugins/woocommerce-blocks/assets/js/data/checkout/reducers.ts Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Comment empty condition * Update CSS classnames * Return null in CreateAccountUI if nothing to display * Linting: Return return param * Document $password param --------- Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
2024-07-15 10:43:02 +00:00
[
clearValidationError,
errorIdString,
setValidationErrors,
label,
customValidityMessage,
]
);
// Allows parent to trigger revalidation.
useImperativeHandle(
forwardedRef,
function () {
return {
[Accessibility] Focus coupon input if it has an error (#48738) * Focus cupon input if it has errors * Add changelog file * Add styles to coupon field with error on cart page * Make coupon errors accessible on the cart page * Add styles to coupon field with error on checkout page * Rename coupon variables on cart * Focus coupon field with error before updating live region on cart page * Focus coupon field with error before updating live region on checkout page * Remove incorrect early return * Update coupon error notice test * Improve coupon error message semantics * Fix coupon errors for block based themes * Update tests to not look for coupon errors on the notice block * Rename coupon-error-message to coupon-error-notice * Fix notice if coupon doesn't exist on tests * FIx invalid coupon notice on classic theme test * Update test for coupon inline notice * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Fix code formatting Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Don't clear coupon input if code doesn't exist * Create coupon error notice mixin * Update coupon error notice styles * Coupon error notice T19 compatibility * Coupon error notice T17 compatibility * Coupon error notice TT1 compatibility * Coupon error notice TT compatibility * Coupon error notice TT2 compatibility * Coupon error notice TT3 compatibility * Add spaces around paramenters Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Add spaces around paramenters Co-authored-by: Darin Kotter <darin.kotter@gmail.com> * Replace $red SCSS variable with a CSS one * Keep input with the invalid coupon code after notice appears * Fix typo in comment Co-authored-by: Mike Jolley <mike.jolley@me.com> * Fix typo in comment Co-authored-by: Mike Jolley <mike.jolley@me.com> * Break notice message into two lines * Break if statement into two lines --------- Co-authored-by: Darin Kotter <darin.kotter@gmail.com> Co-authored-by: Mike Jolley <mike.jolley@me.com>
2024-08-23 18:22:28 +00:00
focus() {
inputRef.current?.focus();
},
revalidate() {
validateInput( ! value );
},
};
},
[ validateInput, value ]
);
/**
* Handle browser autofill / changes via data store.
*
* Trigger validation on incoming state change if the current element is not in focus. This is because autofilled
* elements do not trigger the blur() event, and so values can be validated in the background if the state changes
* elsewhere.
*
* Errors are immediately visible.
*/
useEffect( () => {
if (
value !== previousValue &&
( value || previousValue ) &&
inputRef &&
inputRef.current !== null &&
inputRef.current?.ownerDocument?.activeElement !==
inputRef.current
) {
const formattedValue = customFormatter(
inputRef.current.value
);
if ( formattedValue !== value ) {
onChange( formattedValue );
} else {
validateInput( true );
}
}
}, [ validateInput, customFormatter, value, previousValue, onChange ] );
/**
* Validation on mount.
*
* If the input is in pristine state on mount, focus the element (if focusOnMount is enabled), and validate in the
* background.
*
* Errors are hidden until blur.
*/
useEffect( () => {
if ( ! isPristine ) {
return;
}
setIsPristine( false );
if ( focusOnMount ) {
inputRef.current?.focus();
}
// if validateOnMount is false, only validate input if focusOnMount is also false
if ( validateOnMount || ! focusOnMount ) {
validateInput( true );
}
}, [
validateOnMount,
focusOnMount,
isPristine,
setIsPristine,
validateInput,
] );
// Remove validation errors when unmounted.
useEffect( () => {
return () => {
clearValidationError( errorIdString );
};
}, [ clearValidationError, errorIdString ] );
if ( passedErrorMessage !== '' && isObject( validationError ) ) {
validationError.message = passedErrorMessage;
}
const hasError = validationError?.message && ! validationError?.hidden;
const describedBy =
showError && hasError && validationErrorId
? validationErrorId
: ariaDescribedBy;
return (
<TextInput
className={ clsx( className, {
'has-error': hasError,
} ) }
aria-invalid={ hasError === true }
id={ textInputId }
type={ type }
feedback={
showError && hasError ? (
<ValidationInputError
errorMessage={ passedErrorMessage }
propertyName={ errorIdString }
/>
) : (
feedback
)
}
ref={ inputRef }
onChange={ ( newValue ) => {
// Hide errors while typing.
hideValidationError( errorIdString );
// Validate the input value.
validateInput( true );
// Push the changes up to the parent component.
const formattedValue = customFormatter( newValue );
if ( formattedValue !== value ) {
onChange( formattedValue );
}
} }
onBlur={ () => validateInput( false ) }
ariaDescribedBy={ describedBy }
value={ value }
title="" // This prevents the same error being shown on hover.
label={ label }
{ ...rest }
/>
);
}
);
export default ValidatedTextInput;