2020-03-17 11:45:33 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2020-04-30 09:43:56 +00:00
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
useCallback,
|
|
|
|
useContext,
|
|
|
|
useState,
|
|
|
|
} from '@wordpress/element';
|
2020-03-17 11:45:33 +00:00
|
|
|
import { omit, pickBy } from 'lodash';
|
2020-05-06 10:21:30 +00:00
|
|
|
import isShallowEqual from '@wordpress/is-shallow-equal';
|
2020-03-17 11:45:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef { import('@woocommerce/type-defs/contexts').ValidationContext } ValidationContext
|
2020-09-20 23:54:08 +00:00
|
|
|
* @typedef {import('react')} React
|
2020-03-17 11:45:33 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
const ValidationContext = createContext( {
|
|
|
|
getValidationError: () => '',
|
|
|
|
setValidationErrors: ( errors ) => void errors,
|
|
|
|
clearValidationError: ( property ) => void property,
|
|
|
|
clearAllValidationErrors: () => void null,
|
2020-04-02 09:27:54 +00:00
|
|
|
hideValidationError: () => void null,
|
|
|
|
showValidationError: () => void null,
|
|
|
|
showAllValidationErrors: () => void null,
|
2020-04-06 20:36:19 +00:00
|
|
|
hasValidationErrors: false,
|
2020-04-02 09:27:54 +00:00
|
|
|
getValidationErrorId: ( errorId ) => errorId,
|
2020-03-17 11:45:33 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {ValidationContext} The context values for the validation context.
|
|
|
|
*/
|
|
|
|
export const useValidationContext = () => {
|
|
|
|
return useContext( ValidationContext );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validation context provider
|
|
|
|
*
|
|
|
|
* Any children of this context will be exposed to validation state and helpers
|
|
|
|
* for tracking validation.
|
2020-09-20 23:54:08 +00:00
|
|
|
*
|
|
|
|
* @param {Object} props Incoming props for the component.
|
|
|
|
* @param {React.ReactChildren} props.children What react elements are wrapped by this component.
|
2020-03-17 11:45:33 +00:00
|
|
|
*/
|
|
|
|
export const ValidationContextProvider = ( { children } ) => {
|
|
|
|
const [ validationErrors, updateValidationErrors ] = useState( {} );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This retrieves any validation error message that exists in state for the
|
|
|
|
* given property name.
|
|
|
|
*
|
|
|
|
* @param {string} property The property the error message is for.
|
|
|
|
*
|
2020-03-23 11:22:00 +00:00
|
|
|
* @return {Object} The error object for the given property.
|
2020-03-17 11:45:33 +00:00
|
|
|
*/
|
2020-05-06 10:21:30 +00:00
|
|
|
const getValidationError = useCallback(
|
|
|
|
( property ) => validationErrors[ property ],
|
|
|
|
[ validationErrors ]
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides an id for the validation error that can be used to fill out
|
|
|
|
* aria-describedby attribute values.
|
|
|
|
*
|
|
|
|
* @param {string} errorId The input css id the validation error is related
|
|
|
|
* to.
|
|
|
|
* @return {string} The id to use for the validation error container.
|
|
|
|
*/
|
|
|
|
const getValidationErrorId = useCallback(
|
|
|
|
( errorId ) => {
|
|
|
|
const error = validationErrors[ errorId ];
|
|
|
|
if ( ! error || error.hidden ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return `validate-error-${ errorId }`;
|
|
|
|
},
|
|
|
|
[ validationErrors ]
|
|
|
|
);
|
2020-03-17 11:45:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears any validation error that exists in state for the given property
|
|
|
|
* name.
|
|
|
|
*
|
|
|
|
* @param {string} property The name of the property to clear if exists in
|
|
|
|
* validation error state.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const clearValidationError = useCallback( ( property ) => {
|
2020-05-06 10:21:30 +00:00
|
|
|
updateValidationErrors( ( prevErrors ) => {
|
|
|
|
if ( ! prevErrors[ property ] ) {
|
|
|
|
return prevErrors;
|
|
|
|
}
|
|
|
|
return omit( prevErrors, [ property ] );
|
|
|
|
} );
|
2020-05-10 23:41:10 +00:00
|
|
|
}, [] );
|
2020-03-17 11:45:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears the entire validation error state.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const clearAllValidationErrors = useCallback(
|
|
|
|
() => void updateValidationErrors( {} ),
|
|
|
|
[]
|
|
|
|
);
|
2020-03-17 11:45:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to record new validation errors in the state.
|
|
|
|
*
|
|
|
|
* @param {Object} newErrors An object where keys are the property names the
|
|
|
|
* validation error is for and values are the
|
|
|
|
* validation error message displayed to the user.
|
|
|
|
*/
|
2020-05-08 15:32:20 +00:00
|
|
|
const setValidationErrors = useCallback( ( newErrors ) => {
|
2020-05-06 10:21:30 +00:00
|
|
|
if ( ! newErrors ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateValidationErrors( ( prevErrors ) => {
|
|
|
|
newErrors = pickBy( newErrors, ( error, property ) => {
|
|
|
|
if ( typeof error.message !== 'string' ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( prevErrors.hasOwnProperty( property ) ) {
|
|
|
|
return ! isShallowEqual( prevErrors[ property ], error );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} );
|
|
|
|
if ( Object.values( newErrors ).length === 0 ) {
|
|
|
|
return prevErrors;
|
2020-04-30 09:43:56 +00:00
|
|
|
}
|
2020-05-06 10:21:30 +00:00
|
|
|
return {
|
|
|
|
...prevErrors,
|
|
|
|
...newErrors,
|
|
|
|
};
|
|
|
|
} );
|
2020-05-08 15:32:20 +00:00
|
|
|
}, [] );
|
2020-03-17 11:45:33 +00:00
|
|
|
|
2020-05-06 10:21:30 +00:00
|
|
|
/**
|
|
|
|
* Used to update a validation error.
|
|
|
|
*
|
|
|
|
* @param {string} property The name of the property to update.
|
|
|
|
* @param {Object} newError New validation error object.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const updateValidationError = useCallback( ( property, newError ) => {
|
2020-03-23 11:22:00 +00:00
|
|
|
updateValidationErrors( ( prevErrors ) => {
|
|
|
|
if ( ! prevErrors.hasOwnProperty( property ) ) {
|
|
|
|
return prevErrors;
|
|
|
|
}
|
2020-05-06 10:21:30 +00:00
|
|
|
const updatedError = {
|
|
|
|
...prevErrors[ property ],
|
|
|
|
...newError,
|
2020-03-23 11:22:00 +00:00
|
|
|
};
|
2020-05-06 10:21:30 +00:00
|
|
|
return isShallowEqual( prevErrors[ property ], updatedError )
|
|
|
|
? prevErrors
|
|
|
|
: {
|
|
|
|
...prevErrors,
|
|
|
|
[ property ]: updatedError,
|
|
|
|
};
|
2020-03-23 11:22:00 +00:00
|
|
|
} );
|
2020-05-10 23:41:10 +00:00
|
|
|
}, [] );
|
2020-03-23 11:22:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a property name and if an associated error exists, it sets its
|
|
|
|
* `hidden` value to true.
|
|
|
|
*
|
|
|
|
* @param {string} property The name of the property to set the `hidden`
|
|
|
|
* value to true.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const hideValidationError = useCallback(
|
|
|
|
( property ) =>
|
|
|
|
void updateValidationError( property, {
|
|
|
|
hidden: true,
|
|
|
|
} ),
|
|
|
|
[ updateValidationError ]
|
|
|
|
);
|
2020-03-23 11:22:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a property name and if an associated error exists, it sets its
|
|
|
|
* `hidden` value to false.
|
|
|
|
*
|
|
|
|
* @param {string} property The name of the property to set the `hidden`
|
|
|
|
* value to false.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const showValidationError = useCallback(
|
|
|
|
( property ) =>
|
|
|
|
void updateValidationError( property, {
|
|
|
|
hidden: false,
|
|
|
|
} ),
|
|
|
|
[ updateValidationError ]
|
|
|
|
);
|
2020-03-23 11:22:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the `hidden` value of all errors to `false`.
|
|
|
|
*/
|
2020-05-10 23:41:10 +00:00
|
|
|
const showAllValidationErrors = useCallback(
|
|
|
|
() =>
|
|
|
|
void updateValidationErrors( ( prevErrors ) => {
|
|
|
|
const updatedErrors = {};
|
|
|
|
|
|
|
|
Object.keys( prevErrors ).forEach( ( property ) => {
|
|
|
|
if ( prevErrors[ property ].hidden ) {
|
|
|
|
updatedErrors[ property ] = {
|
|
|
|
...prevErrors[ property ],
|
|
|
|
hidden: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
if ( Object.values( updatedErrors ).length === 0 ) {
|
|
|
|
return prevErrors;
|
2020-05-06 10:21:30 +00:00
|
|
|
}
|
2020-03-23 11:22:00 +00:00
|
|
|
|
2020-05-10 23:41:10 +00:00
|
|
|
return {
|
|
|
|
...prevErrors,
|
|
|
|
...updatedErrors,
|
|
|
|
};
|
|
|
|
} ),
|
|
|
|
[]
|
|
|
|
);
|
2020-03-17 11:45:33 +00:00
|
|
|
|
|
|
|
const context = {
|
|
|
|
getValidationError,
|
|
|
|
setValidationErrors,
|
|
|
|
clearValidationError,
|
|
|
|
clearAllValidationErrors,
|
2020-03-23 11:22:00 +00:00
|
|
|
hideValidationError,
|
|
|
|
showValidationError,
|
|
|
|
showAllValidationErrors,
|
2020-04-06 20:36:19 +00:00
|
|
|
hasValidationErrors: Object.keys( validationErrors ).length > 0,
|
2020-04-02 09:27:54 +00:00
|
|
|
getValidationErrorId,
|
2020-03-17 11:45:33 +00:00
|
|
|
};
|
2020-05-06 10:21:30 +00:00
|
|
|
|
2020-03-17 11:45:33 +00:00
|
|
|
return (
|
|
|
|
<ValidationContext.Provider value={ context }>
|
|
|
|
{ children }
|
|
|
|
</ValidationContext.Provider>
|
|
|
|
);
|
|
|
|
};
|