@form-eng/core 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +43 -12
- package/dist/index.d.ts +43 -12
- package/dist/index.js +77 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -76,7 +76,16 @@ var ComponentTypes = {
|
|
|
76
76
|
ReadOnlyRichText: "ReadOnlyRichText",
|
|
77
77
|
ReadOnlyWithButton: "ReadOnlyWithButton",
|
|
78
78
|
ChoiceSet: "ChoiceSet",
|
|
79
|
-
FieldArray: "FieldArray"
|
|
79
|
+
FieldArray: "FieldArray",
|
|
80
|
+
RadioGroup: "RadioGroup",
|
|
81
|
+
CheckboxGroup: "CheckboxGroup",
|
|
82
|
+
Rating: "Rating",
|
|
83
|
+
ColorPicker: "ColorPicker",
|
|
84
|
+
Autocomplete: "Autocomplete",
|
|
85
|
+
FileUpload: "FileUpload",
|
|
86
|
+
DateRange: "DateRange",
|
|
87
|
+
DateTime: "DateTime",
|
|
88
|
+
PhoneInput: "PhoneInput"
|
|
80
89
|
};
|
|
81
90
|
var FormConstants = {
|
|
82
91
|
defaultExpandCutoffCount: 12,
|
|
@@ -327,7 +336,7 @@ var FormStrings = {
|
|
|
327
336
|
}
|
|
328
337
|
};
|
|
329
338
|
|
|
330
|
-
// src/providers/
|
|
339
|
+
// src/providers/RulesEngineProvider.tsx
|
|
331
340
|
import React from "react";
|
|
332
341
|
|
|
333
342
|
// src/helpers/ConditionEvaluator.ts
|
|
@@ -386,6 +395,16 @@ function evaluateFieldCondition(condition, values) {
|
|
|
386
395
|
} catch {
|
|
387
396
|
return false;
|
|
388
397
|
}
|
|
398
|
+
case "arrayContains":
|
|
399
|
+
return Array.isArray(fieldValue) ? fieldValue.some((v) => looseEquals(v, condition.value)) : false;
|
|
400
|
+
case "arrayNotContains":
|
|
401
|
+
return Array.isArray(fieldValue) ? !fieldValue.some((v) => looseEquals(v, condition.value)) : true;
|
|
402
|
+
case "arrayLengthEquals":
|
|
403
|
+
return Array.isArray(fieldValue) ? fieldValue.length === toNumber(condition.value) : false;
|
|
404
|
+
case "arrayLengthGreaterThan":
|
|
405
|
+
return Array.isArray(fieldValue) ? fieldValue.length > toNumber(condition.value) : false;
|
|
406
|
+
case "arrayLengthLessThan":
|
|
407
|
+
return Array.isArray(fieldValue) ? fieldValue.length < toNumber(condition.value) : false;
|
|
389
408
|
default:
|
|
390
409
|
return false;
|
|
391
410
|
}
|
|
@@ -695,6 +714,9 @@ function evaluateAllRules(fields, values, areAllFieldsReadonly) {
|
|
|
695
714
|
}
|
|
696
715
|
logEvent("rule_evaluated", fieldName, `${config.rules.length} rule(s) evaluated`);
|
|
697
716
|
applyEffectToState(fieldStates, fieldName, ruleResults.selfEffect);
|
|
717
|
+
if (ruleResults.pendingSetValue !== void 0 && fieldStates[fieldName]) {
|
|
718
|
+
fieldStates[fieldName].pendingSetValue = ruleResults.pendingSetValue;
|
|
719
|
+
}
|
|
698
720
|
for (const [targetField, effect] of Object.entries(ruleResults.crossEffects)) {
|
|
699
721
|
if (fieldStates[targetField]) {
|
|
700
722
|
applyEffectToState(fieldStates, targetField, effect);
|
|
@@ -730,7 +752,8 @@ function evaluateAffectedFields(changedField, fields, values, currentState) {
|
|
|
730
752
|
label: config.label,
|
|
731
753
|
defaultValue: config.defaultValue,
|
|
732
754
|
computeOnCreateOnly: config.computeOnCreateOnly,
|
|
733
|
-
activeRuleIds: []
|
|
755
|
+
activeRuleIds: [],
|
|
756
|
+
pendingSetValue: void 0
|
|
734
757
|
};
|
|
735
758
|
}
|
|
736
759
|
for (const [ownerField, config] of Object.entries(fields)) {
|
|
@@ -738,6 +761,9 @@ function evaluateAffectedFields(changedField, fields, values, currentState) {
|
|
|
738
761
|
const ruleResults = evaluateFieldRules(config.rules, values);
|
|
739
762
|
if (affected.has(ownerField) || ownerField === changedField) {
|
|
740
763
|
applyEffectToState(updatedStates, ownerField, ruleResults.selfEffect);
|
|
764
|
+
if (ruleResults.pendingSetValue !== void 0 && updatedStates[ownerField]) {
|
|
765
|
+
updatedStates[ownerField].pendingSetValue = ruleResults.pendingSetValue;
|
|
766
|
+
}
|
|
741
767
|
}
|
|
742
768
|
for (const [targetField, effect] of Object.entries(ruleResults.crossEffects)) {
|
|
743
769
|
if (affected.has(targetField) && updatedStates[targetField]) {
|
|
@@ -771,6 +797,7 @@ function evaluateFieldRules(rules, values) {
|
|
|
771
797
|
const crossEffects = {};
|
|
772
798
|
let fieldOrder;
|
|
773
799
|
const activeRuleIds = [];
|
|
800
|
+
let pendingSetValue;
|
|
774
801
|
for (let i = 0; i < sorted.length; i++) {
|
|
775
802
|
const rule = sorted[i];
|
|
776
803
|
const conditionMet = evaluateCondition(rule.when, values);
|
|
@@ -778,6 +805,9 @@ function evaluateFieldRules(rules, values) {
|
|
|
778
805
|
activeRuleIds.push(rule.id ?? `rule_${i}`);
|
|
779
806
|
}
|
|
780
807
|
const effect = conditionMet ? rule.then : rule.else;
|
|
808
|
+
if (conditionMet && rule.then?.setValue !== void 0 && pendingSetValue === void 0) {
|
|
809
|
+
pendingSetValue = { value: rule.then.setValue };
|
|
810
|
+
}
|
|
781
811
|
if (!effect) continue;
|
|
782
812
|
mergeEffect(selfEffect, effect);
|
|
783
813
|
if (effect.fields) {
|
|
@@ -792,7 +822,7 @@ function evaluateFieldRules(rules, values) {
|
|
|
792
822
|
fieldOrder = effect.fieldOrder;
|
|
793
823
|
}
|
|
794
824
|
}
|
|
795
|
-
return { selfEffect, crossEffects, fieldOrder, activeRuleIds };
|
|
825
|
+
return { selfEffect, crossEffects, fieldOrder, activeRuleIds, pendingSetValue };
|
|
796
826
|
}
|
|
797
827
|
function mergeEffect(target, source) {
|
|
798
828
|
if (source.required !== void 0 && target.required === void 0) {
|
|
@@ -836,7 +866,7 @@ function applyEffectToState(states, fieldName, effect) {
|
|
|
836
866
|
if (effect.computedValue !== void 0) state.computedValue = effect.computedValue;
|
|
837
867
|
}
|
|
838
868
|
|
|
839
|
-
// src/reducers/
|
|
869
|
+
// src/reducers/RulesEngineReducer.ts
|
|
840
870
|
var defaultRulesEngineState = {
|
|
841
871
|
configs: {}
|
|
842
872
|
};
|
|
@@ -883,9 +913,9 @@ var rulesEngineReducer = (state = defaultRulesEngineState, action) => {
|
|
|
883
913
|
return state;
|
|
884
914
|
}
|
|
885
915
|
};
|
|
886
|
-
var
|
|
916
|
+
var RulesEngineReducer_default = rulesEngineReducer;
|
|
887
917
|
|
|
888
|
-
// src/providers/
|
|
918
|
+
// src/providers/RulesEngineProvider.tsx
|
|
889
919
|
import { jsx } from "react/jsx-runtime";
|
|
890
920
|
var RulesEngineContext = React.createContext(
|
|
891
921
|
void 0
|
|
@@ -900,7 +930,7 @@ function UseRulesEngineContext() {
|
|
|
900
930
|
return context;
|
|
901
931
|
}
|
|
902
932
|
var RulesEngineProvider = (props) => {
|
|
903
|
-
const [rulesState, dispatch] = React.useReducer(
|
|
933
|
+
const [rulesState, dispatch] = React.useReducer(RulesEngineReducer_default, defaultRulesEngineState);
|
|
904
934
|
const rulesStateRef = React.useRef(rulesState);
|
|
905
935
|
React.useEffect(() => {
|
|
906
936
|
rulesStateRef.current = rulesState;
|
|
@@ -939,7 +969,7 @@ var RulesEngineProvider = (props) => {
|
|
|
939
969
|
return /* @__PURE__ */ jsx(RulesEngineContext.Provider, { value: providerValue, children: props.children });
|
|
940
970
|
};
|
|
941
971
|
|
|
942
|
-
// src/providers/
|
|
972
|
+
// src/providers/InjectedFieldProvider.tsx
|
|
943
973
|
import React2 from "react";
|
|
944
974
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
945
975
|
var InjectedFieldContext = React2.createContext(
|
|
@@ -1083,6 +1113,19 @@ var requiredIf = (value, params, context) => {
|
|
|
1083
1113
|
}
|
|
1084
1114
|
return void 0;
|
|
1085
1115
|
};
|
|
1116
|
+
var validatorMetadataRegistry = {};
|
|
1117
|
+
function registerValidatorMetadata(name, metadata) {
|
|
1118
|
+
validatorMetadataRegistry = { ...validatorMetadataRegistry, [name]: metadata };
|
|
1119
|
+
}
|
|
1120
|
+
function getValidatorMetadata(name) {
|
|
1121
|
+
return validatorMetadataRegistry[name];
|
|
1122
|
+
}
|
|
1123
|
+
function getAllValidatorMetadata() {
|
|
1124
|
+
return { ...validatorMetadataRegistry };
|
|
1125
|
+
}
|
|
1126
|
+
function resetValidatorMetadataRegistry() {
|
|
1127
|
+
validatorMetadataRegistry = {};
|
|
1128
|
+
}
|
|
1086
1129
|
var defaultValidators = {
|
|
1087
1130
|
required,
|
|
1088
1131
|
email,
|
|
@@ -1382,7 +1425,7 @@ function validateDependencyGraph(fields) {
|
|
|
1382
1425
|
try {
|
|
1383
1426
|
if (typeof globalThis !== "undefined" && globalThis.__DEV__ !== false) {
|
|
1384
1427
|
for (const error of errors) {
|
|
1385
|
-
console.warn(`[
|
|
1428
|
+
console.warn(`[form-engine] ${error.message}`);
|
|
1386
1429
|
}
|
|
1387
1430
|
}
|
|
1388
1431
|
} catch {
|
|
@@ -1688,7 +1731,7 @@ var FieldWrapper = React5.memo((props) => {
|
|
|
1688
1731
|
/* @__PURE__ */ jsxs3("label", { id: labelId, htmlFor: id, className: "field-label", children: [
|
|
1689
1732
|
label,
|
|
1690
1733
|
required2 && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1691
|
-
/* @__PURE__ */ jsx5("span", { className: "required-indicator", "aria-hidden": "true", style: { color: "var(--
|
|
1734
|
+
/* @__PURE__ */ jsx5("span", { className: "required-indicator", "aria-hidden": "true", style: { color: "var(--fe-required-color, #d13438)" }, children: " *" }),
|
|
1692
1735
|
/* @__PURE__ */ jsx5("span", { className: "sr-only", style: { position: "absolute", width: "1px", height: "1px", padding: 0, margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, children: " (required)" })
|
|
1693
1736
|
] })
|
|
1694
1737
|
] }),
|
|
@@ -1696,19 +1739,19 @@ var FieldWrapper = React5.memo((props) => {
|
|
|
1696
1739
|
!additionalInfoComponent && additionalInfo && /* @__PURE__ */ jsx5("span", { className: "additional-info", title: additionalInfo, children: "\u24D8" })
|
|
1697
1740
|
] });
|
|
1698
1741
|
const defaultErrorAndStatus = /* @__PURE__ */ jsx5("div", { className: "message", children: error ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1699
|
-
/* @__PURE__ */ jsx5("span", { className: "error-icon", "aria-hidden": "true", style: { color: "var(--
|
|
1700
|
-
/* @__PURE__ */ jsx5("span", { className: "error-message", id: errorMessageId, role: "alert", style: { color: "var(--
|
|
1742
|
+
/* @__PURE__ */ jsx5("span", { className: "error-icon", "aria-hidden": "true", style: { color: "var(--fe-error-color, #d13438)" }, children: "\u2716" }),
|
|
1743
|
+
/* @__PURE__ */ jsx5("span", { className: "error-message", id: errorMessageId, role: "alert", style: { color: "var(--fe-error-color, #d13438)" }, children: error.message || "Error" })
|
|
1701
1744
|
] }) : savePending ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1702
|
-
/* @__PURE__ */ jsx5("span", { className: "warning-icon", "aria-hidden": "true", style: { color: "var(--
|
|
1703
|
-
/* @__PURE__ */ jsxs3("span", { className: "warning-message", id: errorMessageId, role: "status", style: { color: "var(--
|
|
1745
|
+
/* @__PURE__ */ jsx5("span", { className: "warning-icon", "aria-hidden": "true", style: { color: "var(--fe-warning-color, #ffb900)" }, children: "\u26A0" }),
|
|
1746
|
+
/* @__PURE__ */ jsxs3("span", { className: "warning-message", id: errorMessageId, role: "status", style: { color: "var(--fe-warning-color, #ffb900)" }, children: [
|
|
1704
1747
|
!isManualSave ? FormStrings.autoSavePending : FormStrings.savePending,
|
|
1705
1748
|
" (",
|
|
1706
1749
|
`${errorCount} ${FormStrings.remaining}`,
|
|
1707
1750
|
")"
|
|
1708
1751
|
] })
|
|
1709
1752
|
] }) : saving ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1710
|
-
/* @__PURE__ */ jsx5("span", { className: "save-spinner", "aria-hidden": "true", style: { color: "var(--
|
|
1711
|
-
/* @__PURE__ */ jsx5("span", { className: "save-message", id: errorMessageId, role: "status", style: { color: "var(--
|
|
1753
|
+
/* @__PURE__ */ jsx5("span", { className: "save-spinner", "aria-hidden": "true", style: { color: "var(--fe-saving-color, #0078d4)" }, children: "\u231B" }),
|
|
1754
|
+
/* @__PURE__ */ jsx5("span", { className: "save-message", id: errorMessageId, role: "status", style: { color: "var(--fe-saving-color, #0078d4)" }, children: FormStrings.saving })
|
|
1712
1755
|
] }) : /* @__PURE__ */ jsx5(Fragment, {}) });
|
|
1713
1756
|
return /* @__PURE__ */ jsxs3(
|
|
1714
1757
|
"div",
|
|
@@ -2027,7 +2070,7 @@ var ConfirmInputsModal = (props) => {
|
|
|
2027
2070
|
trigger();
|
|
2028
2071
|
setValue(`${fieldName}`, fieldValue, { shouldDirty: true });
|
|
2029
2072
|
};
|
|
2030
|
-
const content = /* @__PURE__ */ jsx7("div", { className: "
|
|
2073
|
+
const content = /* @__PURE__ */ jsx7("div", { className: "fe-form-wrapper", children: /* @__PURE__ */ jsx7("div", { className: "fe-form-container", children: /* @__PURE__ */ jsx7("form", { className: "fe-form fe-modal", children: confirmInputFields?.map((confirmInputField) => {
|
|
2031
2074
|
const fieldState = rulesState.configs[configName]?.fieldStates[confirmInputField];
|
|
2032
2075
|
const fieldConfig = fields[confirmInputField];
|
|
2033
2076
|
if (!fieldState || !fieldConfig) return null;
|
|
@@ -2055,9 +2098,9 @@ var ConfirmInputsModal = (props) => {
|
|
|
2055
2098
|
if (renderDialog) {
|
|
2056
2099
|
return renderDialog({ isOpen: !!isOpen, onSave: saveConfirmInputFields, onCancel: cancelConfirmInputFields, children: content });
|
|
2057
2100
|
}
|
|
2058
|
-
return /* @__PURE__ */ jsxs5("dialog", { ref: dialogRef, className: "
|
|
2101
|
+
return /* @__PURE__ */ jsxs5("dialog", { ref: dialogRef, className: "fe-modal", role: "dialog", "aria-modal": "true", "aria-label": FormStrings.confirm, onKeyDown: handleKeyDown, children: [
|
|
2059
2102
|
content,
|
|
2060
|
-
/* @__PURE__ */ jsxs5("div", { className: "
|
|
2103
|
+
/* @__PURE__ */ jsxs5("div", { className: "fe-modal-actions", children: [
|
|
2061
2104
|
/* @__PURE__ */ jsx7("button", { type: "button", ref: saveButtonRef, onClick: saveConfirmInputFields, children: FormStrings.save }),
|
|
2062
2105
|
/* @__PURE__ */ jsx7("button", { type: "button", onClick: cancelConfirmInputFields, children: FormStrings.cancel })
|
|
2063
2106
|
] })
|
|
@@ -2129,10 +2172,10 @@ var FormFields = (props) => {
|
|
|
2129
2172
|
const collapsedClass = !isExpanded && (expandEnabled || expandEnabled === void 0) ? "collapsed" : "";
|
|
2130
2173
|
const fieldsToRender = GetFieldsToRender(fieldRenderLimit ?? 0, fieldOrder ?? [], formState?.fieldStates);
|
|
2131
2174
|
const loadingKey = `${programName}-${entityType}-${entityId}-form-loaded`;
|
|
2132
|
-
return /* @__PURE__ */ jsx9("div", { className: `
|
|
2175
|
+
return /* @__PURE__ */ jsx9("div", { className: `fe-form-container ${collapsedClass}`, children: /* @__PURE__ */ jsxs7(
|
|
2133
2176
|
"form",
|
|
2134
2177
|
{
|
|
2135
|
-
className: `
|
|
2178
|
+
className: `fe-form ${collapsedClass} ${inPanel ? "in-panel" : ""}`,
|
|
2136
2179
|
style: collapsedClass && collapsedMaxHeight ? { maxHeight: `${collapsedMaxHeight}px` } : void 0,
|
|
2137
2180
|
"data-testid": `${programName}-${entityType}-${entityId}-form`,
|
|
2138
2181
|
children: [
|
|
@@ -2414,9 +2457,9 @@ var FormEngine = (props) => {
|
|
|
2414
2457
|
const cutoff = expandEnabled && !isExpanded ? effectiveExpandCutoff ?? FormConstants.defaultExpandCutoffCount : void 0;
|
|
2415
2458
|
return /* @__PURE__ */ jsxs8(FormProvider, { ...formMethods, formState: { ...formMethods.formState, isDirty, isValid, dirtyFields, errors, isSubmitting, isSubmitSuccessful }, children: [
|
|
2416
2459
|
/* @__PURE__ */ jsx10("div", { role: "status", "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: "1px", height: "1px", padding: 0, margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, "data-testid": "form-status-live-region", children: statusMessage }),
|
|
2417
|
-
enableFilter && /* @__PURE__ */ jsx10("div", { className: "
|
|
2418
|
-
formErrors && formErrors.length > 0 && /* @__PURE__ */ jsx10("div", { className: "form-errors", role: "alert", style: { color: "var(--
|
|
2419
|
-
/* @__PURE__ */ jsxs8("div", { className: "
|
|
2460
|
+
enableFilter && /* @__PURE__ */ jsx10("div", { className: "fe-filter", children: renderFilterInput ? renderFilterInput({ onChange: onFilterChange }) : /* @__PURE__ */ jsx10("input", { type: "text", placeholder: FormStrings.filterFields, "aria-label": FormStrings.filterFields, onChange: (e) => onFilterChange(e.target.value), className: "fe-filter-input" }) }),
|
|
2461
|
+
formErrors && formErrors.length > 0 && /* @__PURE__ */ jsx10("div", { className: "form-errors", role: "alert", style: { color: "var(--fe-error-color, #d13438)", padding: "8px", marginBottom: "8px" }, children: formErrors.map((err, i) => /* @__PURE__ */ jsx10("div", { className: "form-error-item", children: err }, i)) }),
|
|
2462
|
+
/* @__PURE__ */ jsxs8("div", { className: "fe-form-wrapper", children: [
|
|
2420
2463
|
/* @__PURE__ */ jsx10(
|
|
2421
2464
|
FormFields,
|
|
2422
2465
|
{
|
|
@@ -2443,7 +2486,7 @@ var FormEngine = (props) => {
|
|
|
2443
2486
|
}
|
|
2444
2487
|
),
|
|
2445
2488
|
expandEnabled && (renderExpandButton ? renderExpandButton({ isExpanded, onToggle: () => setIsExpanded(!isExpanded) }) : /* @__PURE__ */ jsx10("button", { className: "expand-button", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, "data-testid": `${programName}-${entityType}-${entityId}-expand-form`, children: isExpanded ? FormStrings.seeLess : FormStrings.expand })),
|
|
2446
|
-
effectiveManualSave && (renderSaveButton ? renderSaveButton({ onSave: manualSave, isDirty, isValid, isSubmitting }) : /* @__PURE__ */ jsxs8("div", { className: "
|
|
2489
|
+
effectiveManualSave && (renderSaveButton ? renderSaveButton({ onSave: manualSave, isDirty, isValid, isSubmitting }) : /* @__PURE__ */ jsxs8("div", { className: "fe-save-actions", style: { marginTop: "16px", display: "flex", gap: "8px" }, children: [
|
|
2447
2490
|
/* @__PURE__ */ jsx10("button", { type: "button", className: "save-button", onClick: manualSave, disabled: !isDirty || isSubmitting, children: isCreate ? FormStrings.create : FormStrings.save }),
|
|
2448
2491
|
/* @__PURE__ */ jsx10("button", { type: "button", className: "cancel-button", onClick: () => {
|
|
2449
2492
|
reset();
|
|
@@ -4170,6 +4213,7 @@ export {
|
|
|
4170
4213
|
extractFunctionDependencies,
|
|
4171
4214
|
flushRenderCycle,
|
|
4172
4215
|
fromRjsfSchema,
|
|
4216
|
+
getAllValidatorMetadata,
|
|
4173
4217
|
getCurrentLocale,
|
|
4174
4218
|
getLastRenderedFields,
|
|
4175
4219
|
getLocaleString,
|
|
@@ -4181,6 +4225,7 @@ export {
|
|
|
4181
4225
|
getTimeline,
|
|
4182
4226
|
getTotalFormRenders,
|
|
4183
4227
|
getValidator,
|
|
4228
|
+
getValidatorMetadata,
|
|
4184
4229
|
getValidatorRegistry,
|
|
4185
4230
|
getValueFunction,
|
|
4186
4231
|
getVisibleSteps,
|
|
@@ -4193,10 +4238,12 @@ export {
|
|
|
4193
4238
|
isStringEmpty,
|
|
4194
4239
|
logEvent,
|
|
4195
4240
|
registerLocale,
|
|
4241
|
+
registerValidatorMetadata,
|
|
4196
4242
|
registerValidators,
|
|
4197
4243
|
registerValueFunctions,
|
|
4198
4244
|
resetLocale,
|
|
4199
4245
|
resetRenderTracker,
|
|
4246
|
+
resetValidatorMetadataRegistry,
|
|
4200
4247
|
resetValidatorRegistry,
|
|
4201
4248
|
resetValueFunctionRegistry,
|
|
4202
4249
|
runSyncValidations,
|