-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: António Capelo <antoniocapelo@users.noreply.github.com> Co-authored-by: João Almeida <eng-almeida@users.noreply.github.com> Co-authored-by: Hanli Theron Co-authored-by: John Brennan <brennj@users.noreply.github.com> Co-authored-by: Dilvane Zanardine <dilvane@users.noreply.github.com> Co-authored-by: Blake Johnston <johnstonbl01@users.noreply.github.com> Co-authored-by: Zofia Korcz <Calanthe@users.noreply.github.com>
- Loading branch information
1 parent
915016c
commit 451772e
Showing
16 changed files
with
8,493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import merge from 'lodash/merge'; | ||
import omit from 'lodash/omit'; | ||
|
||
import { extractParametersFromNode } from './helpers'; | ||
import { supportedTypes } from './internals/fields'; | ||
import { getFieldDescription, pickXKey } from './internals/helpers'; | ||
import { buildYupSchema } from './yupSchema'; | ||
/** | ||
* @typedef {import('./createHeadlessForm').FieldParameters} FieldParameters | ||
*/ | ||
|
||
/** | ||
* Verifies if a field is required | ||
* @param {Object} node - JSON schema parent node | ||
* @param {String} inputName - input name | ||
* @return {Boolean} | ||
*/ | ||
function isFieldRequired(node, inputName) { | ||
// For nested properties (case of fieldset) we need to check recursively | ||
if (node?.required) { | ||
return node.required.includes(inputName); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Loops recursively through fieldset fields and returns an copy version of them | ||
* where the required property is updated. | ||
* | ||
* @param {Array} fields - list of fields of a fieldset | ||
* @param {Object} property - property that relates with the list of fields | ||
* @returns {Object} | ||
*/ | ||
function rebuildInnerFieldsRequiredProperty(fields, property) { | ||
if (property?.properties) { | ||
return fields.map((field) => { | ||
if (field.fields) { | ||
return { | ||
...field, | ||
fields: rebuildInnerFieldsRequiredProperty(field.fields, property.properties[field.name]), | ||
}; | ||
} | ||
return { | ||
...field, | ||
required: isFieldRequired(property, field.name), | ||
}; | ||
}); | ||
} | ||
|
||
return fields.map((field) => ({ | ||
...field, | ||
required: isFieldRequired(property, field.name), | ||
})); | ||
} | ||
|
||
/** | ||
* Builds a function that updates the fields properties based on the form values and the | ||
* dependencies the field has on the current schema. | ||
* @param {FieldParameters} fieldParams - field parameters | ||
* @returns {Function} | ||
*/ | ||
export function calculateConditionalProperties(fieldParams, customProperties) { | ||
/** | ||
* Runs dynamic property calculation on a field based on a conditional that has been calculated | ||
* @param {Boolean} isRequired - if the field is required | ||
* @param {Object} conditionBranch - condition branch being applied | ||
* @returns {Object} updated field parameters | ||
*/ | ||
return (isRequired, conditionBranch) => { | ||
// Check if the current field is conditionally declared in the schema | ||
|
||
const conditionalProperty = conditionBranch?.properties?.[fieldParams.name]; | ||
|
||
if (conditionalProperty) { | ||
const presentation = pickXKey(conditionalProperty, 'presentation') ?? {}; | ||
|
||
const fieldDescription = getFieldDescription(conditionalProperty, customProperties); | ||
|
||
const newFieldParams = extractParametersFromNode({ | ||
...conditionalProperty, | ||
...fieldDescription, | ||
}); | ||
|
||
let fieldSetFields; | ||
|
||
if (fieldParams.inputType === supportedTypes.FIELDSET) { | ||
fieldSetFields = rebuildInnerFieldsRequiredProperty( | ||
fieldParams.fields, | ||
conditionalProperty | ||
); | ||
newFieldParams.fields = fieldSetFields; | ||
} | ||
|
||
const base = { | ||
isVisible: true, | ||
required: isRequired, | ||
...(presentation?.inputType && { type: presentation.inputType }), | ||
schema: buildYupSchema({ | ||
...fieldParams, | ||
...newFieldParams, | ||
// If there are inner fields (case of fieldset) they need to be updated based on the condition | ||
fields: fieldSetFields, | ||
required: isRequired, | ||
}), | ||
}; | ||
|
||
return omit(merge(base, presentation, newFieldParams), ['inputType']); | ||
} | ||
|
||
// If field is not conditionally declared it should be visible if it's required | ||
const isVisible = isRequired; | ||
|
||
return { | ||
isVisible, | ||
required: isRequired, | ||
schema: buildYupSchema({ | ||
...fieldParams, | ||
...extractParametersFromNode(conditionBranch), | ||
required: isRequired, | ||
}), | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import inRange from 'lodash/inRange'; | ||
import isFunction from 'lodash/isFunction'; | ||
import isNil from 'lodash/isNil'; | ||
import isObject from 'lodash/isObject'; | ||
import mapValues from 'lodash/mapValues'; | ||
import pick from 'lodash/pick'; | ||
|
||
import { pickXKey } from './internals/helpers'; | ||
import { buildYupSchema } from './yupSchema'; | ||
|
||
export const SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS = ['minimum', 'maximum']; | ||
|
||
const isCustomValidationAllowed = (fieldParams) => (customValidation, customValidationKey) => { | ||
// don't apply custom validation in cases when the fn returns null. | ||
if (isNil(customValidation)) { | ||
return false; | ||
} | ||
|
||
const { minimum, maximum } = fieldParams; | ||
const isAllowed = inRange( | ||
customValidation, | ||
minimum ?? -Infinity, | ||
maximum ? maximum + 1 : Infinity | ||
); | ||
|
||
if (!isAllowed) { | ||
const errorMessage = `Custom validation for ${fieldParams.name} is not allowed because ${customValidationKey}:${customValidation} is less strict than the original range: ${minimum} to ${maximum}`; | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
throw new Error(errorMessage); | ||
} else { | ||
// eslint-disable-next-line no-console | ||
console.warn(errorMessage); | ||
} | ||
} | ||
|
||
return isAllowed; | ||
}; | ||
|
||
export function calculateCustomValidationProperties(fieldParams, customProperties) { | ||
return (isRequired, conditionBranch, formValues) => { | ||
const params = { ...fieldParams, ...conditionBranch?.properties?.[fieldParams.name] }; | ||
const presentation = pickXKey(params, 'presentation') ?? {}; | ||
|
||
const supportedParams = pick(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS); | ||
|
||
const checkIfAllowed = isCustomValidationAllowed(params); | ||
|
||
const customErrorMessages = []; | ||
const fieldParamsWithNewValidation = mapValues( | ||
supportedParams, | ||
(customValidationValue, customValidationKey) => { | ||
const originalValidation = params[customValidationKey]; | ||
|
||
const customValidation = isFunction(customValidationValue) | ||
? customValidationValue(formValues, params) | ||
: customValidationValue; | ||
|
||
if (isObject(customValidation)) { | ||
if (checkIfAllowed(customValidation[customValidationKey], customValidationKey)) { | ||
customErrorMessages.push(pickXKey(customValidation, 'errorMessage')); | ||
|
||
return customValidation[customValidationKey]; | ||
} | ||
|
||
return originalValidation; | ||
} | ||
|
||
return checkIfAllowed(customValidation, customValidationKey) | ||
? customValidation | ||
: originalValidation; | ||
} | ||
); | ||
|
||
const errorMessage = Object.assign({ ...params.errorMessage }, ...customErrorMessages); | ||
|
||
return { | ||
...params, | ||
...fieldParamsWithNewValidation, | ||
type: presentation?.inputType || params.inputType, | ||
errorMessage, | ||
required: isRequired, | ||
schema: buildYupSchema({ | ||
...params, | ||
...fieldParamsWithNewValidation, | ||
errorMessage, | ||
required: isRequired, | ||
}), | ||
}; | ||
}; | ||
} |
Oops, something went wrong.