@remoteoss/json-schema-form 0.4.3-beta.0 → 0.4.4-dev.20230829101351

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.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.4.3-beta.0
5
- Generated: Wed, 09 Aug 2023 08:39:30 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.4.4-dev.20230829101351
5
+ Generated: Tue, 29 Aug 2023 10:14:13 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -67,8 +67,8 @@ function hasProperty(object2, propertyName) {
67
67
  }
68
68
 
69
69
  // src/checkIfConditionMatches.js
70
- function checkIfConditionMatches(node, formValues, formFields) {
71
- return Object.keys(node.if.properties).every((name) => {
70
+ function checkIfConditionMatches(node, formValues, formFields, validations) {
71
+ return Object.keys(node.if.properties ?? {}).every((name) => {
72
72
  const currentProperty = node.if.properties[name];
73
73
  const value = formValues[name];
74
74
  const hasEmptyValue = typeof value === "undefined" || // NOTE: This is a "Remote API" dependency, as empty fields are sent as "null".
@@ -94,7 +94,8 @@ function checkIfConditionMatches(node, formValues, formFields) {
94
94
  return checkIfConditionMatches(
95
95
  { if: currentProperty },
96
96
  formValues[name],
97
- getField(name, formFields).fields
97
+ getField(name, formFields).fields,
98
+ validations
98
99
  );
99
100
  }
100
101
  const field = getField(name, formFields);
@@ -110,6 +111,23 @@ function checkIfConditionMatches(node, formValues, formFields) {
110
111
  );
111
112
  });
112
113
  }
114
+ function checkIfMatchesValidationsAndComputedValues(node, formValues, validations, parentID) {
115
+ const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
116
+ const currentValue = validations.getScope(parentID).evaluateValidationRuleInCondition(name, formValues);
117
+ if (Object.hasOwn(property, "const") && currentValue === property.const)
118
+ return true;
119
+ return false;
120
+ });
121
+ const computedValuesMatch = Object.entries(node.if.computedValues ?? {}).every(
122
+ ([name, property]) => {
123
+ const currentValue = validations.getScope(parentID).evaluateComputedValueRuleInCondition(name, formValues);
124
+ if (Object.hasOwn(property, "const") && currentValue === property.const)
125
+ return true;
126
+ return false;
127
+ }
128
+ );
129
+ return computedValuesMatch && validationsMatch;
130
+ }
113
131
 
114
132
  // src/internals/helpers.js
115
133
  import merge from "lodash/fp/merge";
@@ -370,6 +388,9 @@ function _composeFieldCustomClosure(defaultComposeFn) {
370
388
  };
371
389
  }
372
390
 
391
+ // src/jsonLogic.js
392
+ import jsonLogic from "json-logic-js";
393
+
373
394
  // src/yupSchema.js
374
395
  import flow from "lodash/flow";
375
396
  import noop from "lodash/noop";
@@ -497,7 +518,7 @@ var getYupSchema = ({ inputType, ...field }) => {
497
518
  }
498
519
  return yupSchemas[inputType] || yupSchemasToJsonTypes[jsonType];
499
520
  };
500
- function buildYupSchema(field, config) {
521
+ function buildYupSchema(field, config, validations) {
501
522
  const { inputType, jsonType: jsonTypeValue, errorMessage = {}, ...propertyFields } = field;
502
523
  const isCheckboxBoolean = typeof propertyFields.checkboxValue === "boolean";
503
524
  let baseSchema;
@@ -570,6 +591,13 @@ function buildYupSchema(field, config) {
570
591
  }) : true
571
592
  );
572
593
  }
594
+ function withConst(yupSchema) {
595
+ return yupSchema.test(
596
+ "isConst",
597
+ errorMessage.const ?? errorMessageFromConfig.const ?? `The only accepted value is ${propertyFields.const}.`,
598
+ (value) => propertyFields.required === false && value === void 0 || value === null || value === propertyFields.const
599
+ );
600
+ }
573
601
  function withBaseSchema() {
574
602
  const customErrorMsg = errorMessage.type || errorMessageFromConfig.type;
575
603
  if (customErrorMsg) {
@@ -590,7 +618,8 @@ function buildYupSchema(field, config) {
590
618
  ...fieldSetfield,
591
619
  inputType: fieldSetfield.type
592
620
  },
593
- config
621
+ { ...config, parentID: field.name },
622
+ validations
594
623
  )();
595
624
  }
596
625
  });
@@ -601,7 +630,11 @@ function buildYupSchema(field, config) {
601
630
  propertyFields.nthFieldGroup.fields().reduce(
602
631
  (schema, groupArrayField) => ({
603
632
  ...schema,
604
- [groupArrayField.name]: buildYupSchema(groupArrayField, config)()
633
+ [groupArrayField.name]: buildYupSchema(
634
+ groupArrayField,
635
+ { ...config, parentID: `${propertyFields.nthFieldGroup.name}[]` },
636
+ validations
637
+ )()
605
638
  }),
606
639
  {}
607
640
  )
@@ -637,6 +670,14 @@ function buildYupSchema(field, config) {
637
670
  if (propertyFields.accept) {
638
671
  validators.push(withFileFormat);
639
672
  }
673
+ if (propertyFields.const) {
674
+ validators.push(withConst);
675
+ }
676
+ if (propertyFields.requiredValidations) {
677
+ propertyFields.requiredValidations.forEach(
678
+ (id) => validators.push(yupSchemaWithCustomJSONLogic({ field, id, validations, config }))
679
+ );
680
+ }
640
681
  return flow(validators);
641
682
  }
642
683
  function getNoSortEdges(fields = []) {
@@ -647,26 +688,322 @@ function getNoSortEdges(fields = []) {
647
688
  return list;
648
689
  }, []);
649
690
  }
650
- function getSchema(fields = [], config) {
691
+ function getSchema(fields = [], config, validations) {
651
692
  const newSchema = {};
652
693
  fields.forEach((field) => {
653
694
  if (field.schema) {
654
695
  if (field.name) {
655
696
  if (field.inputType === supportedTypes.FIELDSET) {
656
- const fieldsetSchema = buildYupSchema(field, config)();
697
+ const fieldsetSchema = buildYupSchema(
698
+ field,
699
+ { ...config, parentID: field.name },
700
+ validations
701
+ )();
657
702
  newSchema[field.name] = fieldsetSchema;
658
703
  } else {
659
704
  newSchema[field.name] = field.schema;
660
705
  }
661
706
  } else {
662
- Object.assign(newSchema, getSchema(field.fields, config));
707
+ Object.assign(newSchema, getSchema(field.fields, config, validations));
663
708
  }
664
709
  }
665
710
  });
666
711
  return newSchema;
667
712
  }
668
- function buildCompleteYupSchema(fields, config) {
669
- return object().shape(getSchema(fields, config), getNoSortEdges(fields));
713
+ function buildCompleteYupSchema(fields, config, validations) {
714
+ return object().shape(getSchema(fields, config, validations), getNoSortEdges(fields));
715
+ }
716
+
717
+ // src/jsonLogic.js
718
+ function createValidationChecker(schema) {
719
+ const scopes = /* @__PURE__ */ new Map();
720
+ function createScopes(jsonSchema, key = "root") {
721
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
722
+ scopes.set(key, createValidationsScope(jsonSchema));
723
+ Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
724
+ if (property.type === "array") {
725
+ createScopes(property.items, `${key2}[]`);
726
+ }
727
+ createScopes(property, key2);
728
+ });
729
+ validateInlineRules(jsonSchema, sampleEmptyObject);
730
+ }
731
+ createScopes(schema);
732
+ return {
733
+ scopes,
734
+ getScope(name = "root") {
735
+ return scopes.get(name);
736
+ }
737
+ };
738
+ }
739
+ function createValidationsScope(schema) {
740
+ const validationMap = /* @__PURE__ */ new Map();
741
+ const computedValuesMap = /* @__PURE__ */ new Map();
742
+ const logic = schema?.["x-jsf-logic"] ?? {
743
+ validations: {},
744
+ computedValues: {}
745
+ };
746
+ const validations = Object.entries(logic.validations ?? {});
747
+ const computedValues = Object.entries(logic.computedValues ?? {});
748
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
749
+ validations.forEach(([id, validation]) => {
750
+ if (!validation.rule) {
751
+ throw Error(`Missing rule for validation with id of: "${id}".`);
752
+ }
753
+ checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
754
+ validationMap.set(id, validation);
755
+ });
756
+ computedValues.forEach(([id, computedValue]) => {
757
+ if (!computedValue.rule) {
758
+ throw Error(`Missing rule for computedValue with id of: "${id}".`);
759
+ }
760
+ checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
761
+ computedValuesMap.set(id, computedValue);
762
+ });
763
+ function evaluateValidation(rule, values) {
764
+ return jsonLogic.apply(rule, clean(values));
765
+ }
766
+ return {
767
+ validationMap,
768
+ computedValuesMap,
769
+ evaluateValidation,
770
+ evaluateValidationRuleInCondition(id, values) {
771
+ const validation = validationMap.get(id);
772
+ if (validation === void 0)
773
+ throw Error(`"${id}" validation in if condition doesn't exist.`);
774
+ return evaluateValidation(validation.rule, values);
775
+ },
776
+ evaluateComputedValueRuleForField(id, values, fieldName) {
777
+ const validation = computedValuesMap.get(id);
778
+ if (validation === void 0)
779
+ throw Error(`"${id}" computedValue in field "${fieldName}" doesn't exist.`);
780
+ return evaluateValidation(validation.rule, values);
781
+ },
782
+ evaluateComputedValueRuleInCondition(id, values) {
783
+ const validation = computedValuesMap.get(id);
784
+ if (validation === void 0)
785
+ throw Error(`"${id}" computedValue in if condition doesn't exist.`);
786
+ return evaluateValidation(validation.rule, values);
787
+ }
788
+ };
789
+ }
790
+ function clean(values = {}) {
791
+ return Object.entries(values).reduce((prev, [key, value]) => {
792
+ return { ...prev, [key]: value === void 0 ? null : value };
793
+ }, {});
794
+ }
795
+ function yupSchemaWithCustomJSONLogic({ field, validations, config, id }) {
796
+ const { parentID = "root" } = config;
797
+ const validation = validations.getScope(parentID).validationMap.get(id);
798
+ if (validation === void 0) {
799
+ throw Error(`Validation "${id}" required for "${field.name}" doesn't exist.`);
800
+ }
801
+ return (yupSchema) => yupSchema.test(
802
+ `${field.name}-validation-${id}`,
803
+ validation?.errorMessage ?? "This field is invalid.",
804
+ (value, { parent }) => {
805
+ if (value === void 0 && !field.required)
806
+ return true;
807
+ return jsonLogic.apply(validation.rule, parent);
808
+ }
809
+ );
810
+ }
811
+ function replaceHandlebarsTemplates({
812
+ value: toReplace,
813
+ validations,
814
+ formValues,
815
+ parentID,
816
+ name: fieldName
817
+ }) {
818
+ if (typeof toReplace === "string") {
819
+ return toReplace.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
820
+ return validations.getScope(parentID).evaluateComputedValueRuleForField(key.trim(), formValues, fieldName);
821
+ });
822
+ } else if (typeof toReplace === "object") {
823
+ const { value, ...rules } = toReplace;
824
+ if (Object.keys(rules).length > 1 && !value)
825
+ throw Error("Cannot define multiple rules without a template string with key `value`.");
826
+ const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
827
+ const computedValue = validations.getScope(parentID).evaluateValidation(rule, formValues);
828
+ return prev.replaceAll(`{{${key}}}`, computedValue);
829
+ }, value);
830
+ return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
831
+ return validations.getScope(parentID).evaluateComputedValueRuleForField(key.trim(), formValues, fieldName);
832
+ });
833
+ }
834
+ }
835
+ function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
836
+ return ({ validations, isRequired, config, formValues }) => {
837
+ const { name, computedAttributes } = fieldParams;
838
+ const attributes = Object.fromEntries(
839
+ Object.entries(computedAttributes).map(handleComputedAttribute(validations, formValues, parentID, name)).filter(([, value]) => value !== null)
840
+ );
841
+ return {
842
+ ...attributes,
843
+ schema: buildYupSchema(
844
+ { ...fieldParams, ...attributes, required: isRequired },
845
+ config,
846
+ validations
847
+ )
848
+ };
849
+ };
850
+ }
851
+ function handleComputedAttribute(validations, formValues, parentID, name) {
852
+ return ([key, value]) => {
853
+ if (key === "description")
854
+ return [key, replaceHandlebarsTemplates({ value, validations, formValues, parentID, name })];
855
+ if (key === "title") {
856
+ return [
857
+ "label",
858
+ replaceHandlebarsTemplates({ value, validations, formValues, parentID, name })
859
+ ];
860
+ }
861
+ if (key === "const")
862
+ return [
863
+ key,
864
+ validations.getScope(parentID).evaluateComputedValueRuleForField(value, formValues, name)
865
+ ];
866
+ if (key === "x-jsf-errorMessage") {
867
+ return [
868
+ "errorMessage",
869
+ handleNestedObjectForComputedValues(value, formValues, parentID, validations, name)
870
+ ];
871
+ }
872
+ if (typeof value === "string") {
873
+ return [
874
+ key,
875
+ validations.getScope(parentID).evaluateComputedValueRuleForField(value, formValues, name)
876
+ ];
877
+ }
878
+ if (key === "x-jsf-presentation" && value.statement) {
879
+ return [
880
+ "statement",
881
+ handleNestedObjectForComputedValues(
882
+ value.statement,
883
+ formValues,
884
+ parentID,
885
+ validations,
886
+ name
887
+ )
888
+ ];
889
+ }
890
+ if (typeof value === "object" && value.rule) {
891
+ return [key, validations.getScope(parentID).evaluateValidation(value.rule, formValues)];
892
+ }
893
+ };
894
+ }
895
+ function handleNestedObjectForComputedValues(values, formValues, parentID, validations, name) {
896
+ return Object.fromEntries(
897
+ Object.entries(values).map(([key, value]) => {
898
+ return [key, replaceHandlebarsTemplates({ value, validations, formValues, parentID, name })];
899
+ })
900
+ );
901
+ }
902
+ function processJSONLogicNode({
903
+ node,
904
+ formFields,
905
+ formValues,
906
+ accRequired,
907
+ parentID,
908
+ validations
909
+ }) {
910
+ const requiredFields = new Set(accRequired);
911
+ if (node.allOf) {
912
+ node.allOf.map(
913
+ (allOfNode) => processJSONLogicNode({ node: allOfNode, formValues, formFields, validations, parentID })
914
+ ).forEach(({ required: allOfItemRequired }) => {
915
+ allOfItemRequired.forEach(requiredFields.add, requiredFields);
916
+ });
917
+ }
918
+ if (node.if) {
919
+ const matchesPropertyCondition = checkIfConditionMatches(
920
+ node,
921
+ formValues,
922
+ formFields,
923
+ validations
924
+ );
925
+ const matchesValidationsAndComputedValues = checkIfMatchesValidationsAndComputedValues(
926
+ node,
927
+ formValues,
928
+ validations,
929
+ parentID
930
+ );
931
+ const isConditionMatch = matchesPropertyCondition && matchesValidationsAndComputedValues;
932
+ if (isConditionMatch && node.then) {
933
+ const { required: branchRequired } = processNode({
934
+ node: node.then,
935
+ formValues,
936
+ formFields,
937
+ accRequired,
938
+ validations
939
+ });
940
+ branchRequired.forEach((field) => requiredFields.add(field));
941
+ }
942
+ if (!isConditionMatch && node.else) {
943
+ const { required: branchRequired } = processNode({
944
+ node: node.else,
945
+ formValues,
946
+ formFields,
947
+ accRequired: requiredFields,
948
+ validations
949
+ });
950
+ branchRequired.forEach((field) => requiredFields.add(field));
951
+ }
952
+ }
953
+ return { required: requiredFields };
954
+ }
955
+ function buildSampleEmptyObject(schema = {}) {
956
+ const sample = {};
957
+ if (typeof schema !== "object" || !schema.properties) {
958
+ return schema;
959
+ }
960
+ for (const key in schema.properties) {
961
+ if (schema.properties[key].type === "object") {
962
+ sample[key] = buildSampleEmptyObject(schema.properties[key]);
963
+ } else if (schema.properties[key].type === "array") {
964
+ const itemSchema = schema.properties[key].items;
965
+ sample[key] = buildSampleEmptyObject(itemSchema);
966
+ } else {
967
+ sample[key] = true;
968
+ }
969
+ }
970
+ return sample;
971
+ }
972
+ function validateInlineRules(jsonSchema, sampleEmptyObject) {
973
+ const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
974
+ Object.entries(properties).filter(([, property]) => property["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property]) => {
975
+ Object.entries(property["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
976
+ Object.values(item).forEach((rule) => {
977
+ checkRuleIntegrity(
978
+ rule,
979
+ fieldName,
980
+ sampleEmptyObject,
981
+ (item2) => `"${item2.var}" in inline rule in property "${fieldName}.x-jsf-logic-computedAttrs.${key}" does not exist as a JSON schema property.`
982
+ );
983
+ });
984
+ });
985
+ });
986
+ }
987
+ function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `"${item.var}" in rule "${id}" does not exist as a JSON schema property.`) {
988
+ Object.values(rule ?? {}).map((subRule) => {
989
+ if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
990
+ return;
991
+ subRule.map((item) => {
992
+ const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
993
+ if (isVar) {
994
+ const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, data);
995
+ if (exists === null) {
996
+ throw Error(errorMessage(item));
997
+ }
998
+ } else {
999
+ checkRuleIntegrity(item, id, data);
1000
+ }
1001
+ });
1002
+ });
1003
+ }
1004
+ function removeIndicesFromPath(path) {
1005
+ const intermediatePath = path.replace(/\.\d+\./g, ".");
1006
+ return intermediatePath.replace(/\.\d+$/, "");
670
1007
  }
671
1008
 
672
1009
  // src/helpers.js
@@ -676,8 +1013,8 @@ function hasType(type, typeName) {
676
1013
  function getField(fieldName, fields) {
677
1014
  return fields.find(({ name }) => name === fieldName);
678
1015
  }
679
- function validateFieldSchema(field, value) {
680
- const validator = buildYupSchema(field);
1016
+ function validateFieldSchema(field, value, validations) {
1017
+ const validator = buildYupSchema(field, {}, validations);
681
1018
  return validator().isValidSync(value);
682
1019
  }
683
1020
  function compareFormValueWithSchemaValue(formValue, schemaValue) {
@@ -742,7 +1079,7 @@ function getPrefillValues(fields, initialValues = {}) {
742
1079
  });
743
1080
  return initialValues;
744
1081
  }
745
- function updateField(field, requiredFields, node, formValues) {
1082
+ function updateField(field, requiredFields, node, formValues, validations, config) {
746
1083
  if (!field) {
747
1084
  return;
748
1085
  }
@@ -764,8 +1101,25 @@ function updateField(field, requiredFields, node, formValues) {
764
1101
  }
765
1102
  }
766
1103
  });
1104
+ if (field.getComputedAttributes) {
1105
+ const computedFieldValues = field.getComputedAttributes({
1106
+ field,
1107
+ isRequired: fieldIsRequired,
1108
+ node,
1109
+ formValues,
1110
+ config,
1111
+ validations
1112
+ });
1113
+ updateValues(computedFieldValues);
1114
+ }
767
1115
  if (field.calculateConditionalProperties) {
768
- const newFieldValues = field.calculateConditionalProperties(fieldIsRequired, node);
1116
+ const newFieldValues = field.calculateConditionalProperties(
1117
+ fieldIsRequired,
1118
+ node,
1119
+ validations,
1120
+ config,
1121
+ formValues
1122
+ );
769
1123
  updateValues(newFieldValues);
770
1124
  }
771
1125
  if (field.calculateCustomValidationProperties) {
@@ -777,33 +1131,46 @@ function updateField(field, requiredFields, node, formValues) {
777
1131
  updateValues(newFieldValues);
778
1132
  }
779
1133
  }
780
- function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */ new Set()) {
1134
+ function processNode({
1135
+ node,
1136
+ formValues,
1137
+ formFields,
1138
+ accRequired = /* @__PURE__ */ new Set(),
1139
+ parentID = "root",
1140
+ validations
1141
+ }) {
781
1142
  const requiredFields = new Set(accRequired);
782
1143
  Object.keys(node.properties ?? []).forEach((fieldName) => {
783
1144
  const field = getField(fieldName, formFields);
784
- updateField(field, requiredFields, node, formValues);
1145
+ updateField(field, requiredFields, node, formValues, validations, { parentID });
785
1146
  });
786
1147
  node.required?.forEach((fieldName) => {
787
1148
  requiredFields.add(fieldName);
788
- updateField(getField(fieldName, formFields), requiredFields, node, formValues);
1149
+ updateField(getField(fieldName, formFields), requiredFields, node, formValues, validations, {
1150
+ parentID
1151
+ });
789
1152
  });
790
1153
  if (node.if) {
791
- const matchesCondition = checkIfConditionMatches(node, formValues, formFields);
1154
+ const matchesCondition = checkIfConditionMatches(node, formValues, formFields, validations);
792
1155
  if (matchesCondition && node.then) {
793
- const { required: branchRequired } = processNode(
794
- node.then,
1156
+ const { required: branchRequired } = processNode({
1157
+ node: node.then,
795
1158
  formValues,
796
1159
  formFields,
797
- requiredFields
798
- );
1160
+ accRequired: requiredFields,
1161
+ parentID,
1162
+ validations
1163
+ });
799
1164
  branchRequired.forEach((field) => requiredFields.add(field));
800
1165
  } else if (node.else) {
801
- const { required: branchRequired } = processNode(
802
- node.else,
1166
+ const { required: branchRequired } = processNode({
1167
+ node: node.else,
803
1168
  formValues,
804
1169
  formFields,
805
- requiredFields
806
- );
1170
+ accRequired: requiredFields,
1171
+ parentID,
1172
+ validations
1173
+ });
807
1174
  branchRequired.forEach((field) => requiredFields.add(field));
808
1175
  }
809
1176
  }
@@ -815,12 +1182,21 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
815
1182
  node.anyOf.forEach(({ required = [] }) => {
816
1183
  required.forEach((fieldName) => {
817
1184
  const field = getField(fieldName, formFields);
818
- updateField(field, requiredFields, node, formValues);
1185
+ updateField(field, requiredFields, node, formValues, validations, { parentID });
819
1186
  });
820
1187
  });
821
1188
  }
822
1189
  if (node.allOf) {
823
- node.allOf.map((allOfNode) => processNode(allOfNode, formValues, formFields, requiredFields)).forEach(({ required: allOfItemRequired }) => {
1190
+ node.allOf.map(
1191
+ (allOfNode) => processNode({
1192
+ node: allOfNode,
1193
+ formValues,
1194
+ formFields,
1195
+ accRequired: requiredFields,
1196
+ parentID,
1197
+ validations
1198
+ })
1199
+ ).forEach(({ required: allOfItemRequired }) => {
824
1200
  allOfItemRequired.forEach(requiredFields.add, requiredFields);
825
1201
  });
826
1202
  }
@@ -828,10 +1204,27 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
828
1204
  Object.entries(node.properties).forEach(([name, nestedNode]) => {
829
1205
  const inputType = getInputType(nestedNode);
830
1206
  if (inputType === supportedTypes.FIELDSET) {
831
- processNode(nestedNode, formValues[name] || {}, getField(name, formFields).fields);
1207
+ processNode({
1208
+ node: nestedNode,
1209
+ formValues: formValues[name] || {},
1210
+ formFields: getField(name, formFields).fields,
1211
+ validations,
1212
+ parentID: name
1213
+ });
832
1214
  }
833
1215
  });
834
1216
  }
1217
+ if (node["x-jsf-logic"]) {
1218
+ const { required: requiredFromLogic } = processJSONLogicNode({
1219
+ node: node["x-jsf-logic"],
1220
+ formValues,
1221
+ formFields,
1222
+ accRequired: requiredFields,
1223
+ parentID,
1224
+ validations
1225
+ });
1226
+ requiredFromLogic.forEach((field) => requiredFields.add(field));
1227
+ }
835
1228
  return {
836
1229
  required: requiredFields
837
1230
  };
@@ -846,11 +1239,11 @@ function clearValuesIfNotVisible(fields, formValues) {
846
1239
  }
847
1240
  });
848
1241
  }
849
- function updateFieldsProperties(fields, formValues, jsonSchema) {
1242
+ function updateFieldsProperties(fields, formValues, jsonSchema, validations) {
850
1243
  if (!jsonSchema?.properties) {
851
1244
  return;
852
1245
  }
853
- processNode(jsonSchema, formValues, fields);
1246
+ processNode({ node: jsonSchema, formValues, formFields: fields, validations });
854
1247
  clearValuesIfNotVisible(fields, formValues);
855
1248
  }
856
1249
  var notNullOption = (opt) => opt.const !== null;
@@ -893,11 +1286,20 @@ function extractParametersFromNode(schemaNode) {
893
1286
  }
894
1287
  const presentation = pickXKey(schemaNode, "presentation") ?? {};
895
1288
  const errorMessage = pickXKey(schemaNode, "errorMessage") ?? {};
1289
+ const requiredValidations = schemaNode["x-jsf-logic-validations"];
1290
+ const computedAttributes = schemaNode["x-jsf-logic-computedAttrs"];
1291
+ const decoratedComputedAttributes = {
1292
+ ...computedAttributes ?? {},
1293
+ ...computedAttributes?.const && computedAttributes?.default ? { value: computedAttributes.const } : {}
1294
+ };
896
1295
  const node = omit(schemaNode, ["x-jsf-presentation", "presentation"]);
897
1296
  const description = presentation?.description || node.description;
898
1297
  const statementDescription = containsHTML(presentation.statement?.description) ? wrapWithSpan(presentation.statement.description, { class: "jsf-statement" }) : presentation.statement?.description;
899
1298
  return omitBy(
900
1299
  {
1300
+ const: node.const,
1301
+ // This is a "forced value" when both const and default are present.
1302
+ ...node.const && node.default ? { value: node.const } : {},
901
1303
  label: node.title,
902
1304
  readOnly: node.readOnly,
903
1305
  ...node.deprecated && {
@@ -932,6 +1334,8 @@ function extractParametersFromNode(schemaNode) {
932
1334
  },
933
1335
  // Handle [name].presentation
934
1336
  ...presentation,
1337
+ requiredValidations,
1338
+ computedAttributes: decoratedComputedAttributes,
935
1339
  description: containsHTML(description) ? wrapWithSpan(description, {
936
1340
  class: "jsf-description"
937
1341
  }) : description,
@@ -968,9 +1372,9 @@ function yupToFormErrors(yupError) {
968
1372
  }
969
1373
  return errors;
970
1374
  }
971
- var handleValuesChange = (fields, jsonSchema, config) => (values) => {
972
- updateFieldsProperties(fields, values, jsonSchema);
973
- const lazySchema = lazy(() => buildCompleteYupSchema(fields, config));
1375
+ var handleValuesChange = (fields, jsonSchema, config, validations) => (values) => {
1376
+ updateFieldsProperties(fields, values, jsonSchema, validations);
1377
+ const lazySchema = lazy(() => buildCompleteYupSchema(fields, config, validations));
974
1378
  let errors;
975
1379
  try {
976
1380
  lazySchema.validateSync(values, {
@@ -1024,8 +1428,8 @@ function rebuildFieldset(fields, property) {
1024
1428
  required: isFieldRequired(property, field)
1025
1429
  }));
1026
1430
  }
1027
- function calculateConditionalProperties(fieldParams, customProperties) {
1028
- return (isRequired, conditionBranch) => {
1431
+ function calculateConditionalProperties(fieldParams, customProperties, validations, config) {
1432
+ return (isRequired, conditionBranch, __, _, formValues) => {
1029
1433
  const conditionalProperty = conditionBranch?.properties?.[fieldParams.name];
1030
1434
  if (conditionalProperty) {
1031
1435
  const presentation = pickXKey(conditionalProperty, "presentation") ?? {};
@@ -1039,17 +1443,31 @@ function calculateConditionalProperties(fieldParams, customProperties) {
1039
1443
  fieldSetFields = rebuildFieldset(fieldParams.fields, conditionalProperty);
1040
1444
  newFieldParams.fields = fieldSetFields;
1041
1445
  }
1446
+ const { computedAttributes, ...restNewFieldParams } = newFieldParams;
1447
+ const calculatedComputedAttributes = computedAttributes ? calculateComputedAttributes(newFieldParams, config)({ validations, formValues }) : {};
1448
+ const requiredValidations = [
1449
+ ...fieldParams.requiredValidations ?? [],
1450
+ ...restNewFieldParams.requiredValidations ?? []
1451
+ ];
1042
1452
  const base = {
1043
1453
  isVisible: true,
1044
1454
  required: isRequired,
1045
1455
  ...presentation?.inputType && { type: presentation.inputType },
1046
- schema: buildYupSchema({
1047
- ...fieldParams,
1048
- ...newFieldParams,
1049
- // If there are inner fields (case of fieldset) they need to be updated based on the condition
1050
- fields: fieldSetFields,
1051
- required: isRequired
1052
- })
1456
+ ...calculatedComputedAttributes,
1457
+ ...calculatedComputedAttributes.value ? { value: calculatedComputedAttributes.value } : { value: void 0 },
1458
+ schema: buildYupSchema(
1459
+ {
1460
+ ...fieldParams,
1461
+ ...restNewFieldParams,
1462
+ ...calculatedComputedAttributes,
1463
+ requiredValidations,
1464
+ // If there are inner fields (case of fieldset) they need to be updated based on the condition
1465
+ fields: fieldSetFields,
1466
+ required: isRequired
1467
+ },
1468
+ config,
1469
+ validations
1470
+ )
1053
1471
  };
1054
1472
  return omit2(merge2(base, presentation, newFieldParams), ["inputType"]);
1055
1473
  }
@@ -1143,14 +1561,19 @@ function sortByOrderOrPosition(a, b, order) {
1143
1561
  function removeInvalidAttributes(fields) {
1144
1562
  return omit3(fields, ["items", "maxFileSize", "isDynamic"]);
1145
1563
  }
1146
- function buildFieldParameters(name, fieldProperties, required = [], config = {}) {
1564
+ function buildFieldParameters(name, fieldProperties, required = [], config = {}, validations) {
1147
1565
  const { position } = pickXKey(fieldProperties, "presentation") ?? {};
1148
1566
  let fields;
1149
1567
  const inputType = getInputType(fieldProperties, config.strictInputType, name);
1150
1568
  if (inputType === supportedTypes.FIELDSET) {
1151
- fields = getFieldsFromJSONSchema(fieldProperties, {
1152
- customProperties: get3(config, `customProperties.${name}`, {})
1153
- });
1569
+ fields = getFieldsFromJSONSchema(
1570
+ fieldProperties,
1571
+ {
1572
+ customProperties: get3(config, `customProperties.${name}`, {}),
1573
+ parentID: name
1574
+ },
1575
+ validations
1576
+ );
1154
1577
  }
1155
1578
  const result = {
1156
1579
  name,
@@ -1165,9 +1588,9 @@ function buildFieldParameters(name, fieldProperties, required = [], config = {})
1165
1588
  };
1166
1589
  return omitBy2(result, isNil3);
1167
1590
  }
1168
- function convertJSONSchemaPropertiesToFieldParameters({ properties, required, "x-jsf-order": order }, config = {}) {
1591
+ function convertJSONSchemaPropertiesToFieldParameters({ properties, required, "x-jsf-order": order }, config = {}, validations) {
1169
1592
  const sortFields = (a, b) => sortByOrderOrPosition(a, b, order);
1170
- return Object.entries(properties).filter(([, value]) => typeof value === "object").map(([key, value]) => buildFieldParameters(key, value, required, config)).sort(sortFields).map(({ position, ...fieldParams }) => fieldParams);
1593
+ return Object.entries(properties).filter(([, value]) => typeof value === "object").map(([key, value]) => buildFieldParameters(key, value, required, config, validations)).sort(sortFields).map(({ position, ...fieldParams }) => fieldParams);
1171
1594
  }
1172
1595
  function applyFieldsDependencies(fieldsParameters, node) {
1173
1596
  if (node?.then) {
@@ -1189,6 +1612,9 @@ function applyFieldsDependencies(fieldsParameters, node) {
1189
1612
  applyFieldsDependencies(fieldsParameters, condition);
1190
1613
  });
1191
1614
  }
1615
+ if (node?.["x-jsf-logic"]) {
1616
+ applyFieldsDependencies(fieldsParameters, node["x-jsf-logic"]);
1617
+ }
1192
1618
  }
1193
1619
  function getCustomPropertiesForField(fieldParams, config) {
1194
1620
  return config?.customProperties?.[fieldParams.name];
@@ -1200,15 +1626,16 @@ function getComposeFunctionForField(fieldParams, hasCustomizations) {
1200
1626
  }
1201
1627
  return composeFn;
1202
1628
  }
1203
- function buildField(fieldParams, config, scopedJsonSchema) {
1629
+ function buildField(fieldParams, config, scopedJsonSchema, validations) {
1204
1630
  const customProperties = getCustomPropertiesForField(fieldParams, config);
1205
1631
  const composeFn = getComposeFunctionForField(fieldParams, !!customProperties);
1206
- const yupSchema = buildYupSchema(fieldParams, config);
1207
- const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties);
1632
+ const yupSchema = buildYupSchema(fieldParams, config, validations);
1633
+ const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties, validations, config);
1208
1634
  const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties(
1209
1635
  fieldParams,
1210
1636
  customProperties
1211
1637
  );
1638
+ const getComputedAttributes = Object.keys(fieldParams.computedAttributes).length > 0 && calculateComputedAttributes(fieldParams, config);
1212
1639
  const hasCustomValidations = !!customProperties && size(pick2(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS)) > 0;
1213
1640
  const finalFieldParams = {
1214
1641
  // invalid attribute cleanup
@@ -1221,6 +1648,7 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1221
1648
  ...hasCustomValidations && {
1222
1649
  calculateCustomValidationProperties: calculateCustomValidationPropertiesClosure
1223
1650
  },
1651
+ ...getComputedAttributes && { getComputedAttributes },
1224
1652
  // field customization properties
1225
1653
  ...customProperties && { fieldCustomization: customProperties },
1226
1654
  // base schema
@@ -1229,11 +1657,15 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1229
1657
  };
1230
1658
  return composeFn(finalFieldParams);
1231
1659
  }
1232
- function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1660
+ function getFieldsFromJSONSchema(scopedJsonSchema, config, validations) {
1233
1661
  if (!scopedJsonSchema) {
1234
1662
  return [];
1235
1663
  }
1236
- const fieldParamsList = convertJSONSchemaPropertiesToFieldParameters(scopedJsonSchema, config);
1664
+ const fieldParamsList = convertJSONSchemaPropertiesToFieldParameters(
1665
+ scopedJsonSchema,
1666
+ config,
1667
+ validations
1668
+ );
1237
1669
  applyFieldsDependencies(fieldParamsList, scopedJsonSchema);
1238
1670
  const fields = [];
1239
1671
  fieldParamsList.forEach((fieldParams) => {
@@ -1253,11 +1685,11 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1253
1685
  fields: () => groupArrayFields,
1254
1686
  addFieldText: fieldParams.addFieldText
1255
1687
  };
1256
- buildField(fieldParams, config, scopedJsonSchema).forEach((groupField) => {
1688
+ buildField(fieldParams, config, scopedJsonSchema, validations).forEach((groupField) => {
1257
1689
  fields.push(groupField);
1258
1690
  });
1259
1691
  } else {
1260
- fields.push(buildField(fieldParams, config, scopedJsonSchema));
1692
+ fields.push(buildField(fieldParams, config, scopedJsonSchema, validations));
1261
1693
  }
1262
1694
  });
1263
1695
  return fields;
@@ -1268,9 +1700,15 @@ function createHeadlessForm(jsonSchema, customConfig = {}) {
1268
1700
  ...customConfig
1269
1701
  };
1270
1702
  try {
1271
- const fields = getFieldsFromJSONSchema(jsonSchema, config);
1272
- const handleValidation = handleValuesChange(fields, jsonSchema, config);
1273
- updateFieldsProperties(fields, getPrefillValues(fields, config.initialValues), jsonSchema);
1703
+ const validations = createValidationChecker(jsonSchema);
1704
+ const fields = getFieldsFromJSONSchema(jsonSchema, config, validations);
1705
+ const handleValidation = handleValuesChange(fields, jsonSchema, config, validations);
1706
+ updateFieldsProperties(
1707
+ fields,
1708
+ getPrefillValues(fields, config.initialValues),
1709
+ jsonSchema,
1710
+ validations
1711
+ );
1274
1712
  return {
1275
1713
  fields,
1276
1714
  handleValidation,