@remoteoss/json-schema-form 0.4.5-beta.0 → 0.5.0-beta.0

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.5-beta.0
5
- Generated: Thu, 31 Aug 2023 07:23:05 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.5.0-beta.0
5
+ Generated: Tue, 12 Sep 2023 07:51:25 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -375,6 +375,103 @@ import flow from "lodash/flow";
375
375
  import noop from "lodash/noop";
376
376
  import { randexp } from "randexp";
377
377
  import { string, number, boolean, object, array } from "yup";
378
+
379
+ // src/jsonLogic.js
380
+ import jsonLogic from "json-logic-js";
381
+ function createValidationChecker(schema) {
382
+ const scopes = /* @__PURE__ */ new Map();
383
+ function createScopes(jsonSchema, key = "root") {
384
+ scopes.set(key, createValidationsScope(jsonSchema));
385
+ Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
386
+ if (property.type === "array") {
387
+ createScopes(property.items, `${key2}[]`);
388
+ } else {
389
+ createScopes(property, key2);
390
+ }
391
+ });
392
+ }
393
+ createScopes(schema);
394
+ return {
395
+ scopes,
396
+ getScope(name = "root") {
397
+ return scopes.get(name);
398
+ }
399
+ };
400
+ }
401
+ function createValidationsScope(schema) {
402
+ const validationMap = /* @__PURE__ */ new Map();
403
+ const computedValuesMap = /* @__PURE__ */ new Map();
404
+ const logic = schema?.["x-jsf-logic"] ?? {
405
+ validations: {},
406
+ computedValues: {}
407
+ };
408
+ const validations = Object.entries(logic.validations ?? {});
409
+ const computedValues = Object.entries(logic.computedValues ?? {});
410
+ validations.forEach(([id, validation]) => {
411
+ validationMap.set(id, validation);
412
+ });
413
+ computedValues.forEach(([id, computedValue]) => {
414
+ computedValuesMap.set(id, computedValue);
415
+ });
416
+ function validate(rule, values) {
417
+ return jsonLogic.apply(rule, replaceUndefinedValuesWithNulls(values));
418
+ }
419
+ return {
420
+ validationMap,
421
+ computedValuesMap,
422
+ validate,
423
+ applyValidationRuleInCondition(id, values) {
424
+ const validation = validationMap.get(id);
425
+ return validate(validation.rule, values);
426
+ },
427
+ applyComputedValueInField(id, values) {
428
+ const validation = computedValuesMap.get(id);
429
+ return validate(validation.rule, values);
430
+ },
431
+ applyComputedValueRuleInCondition(id, values) {
432
+ const validation = computedValuesMap.get(id);
433
+ return validate(validation.rule, values);
434
+ }
435
+ };
436
+ }
437
+ function replaceUndefinedValuesWithNulls(values = {}) {
438
+ return Object.entries(values).reduce((prev, [key, value]) => {
439
+ return { ...prev, [key]: value === void 0 ? null : value };
440
+ }, {});
441
+ }
442
+ function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
443
+ const { parentID = "root" } = config;
444
+ const validation = logic.getScope(parentID).validationMap.get(id);
445
+ return (yupSchema) => yupSchema.test(
446
+ `${field.name}-validation-${id}`,
447
+ validation?.errorMessage ?? "This field is invalid.",
448
+ (value, { parent }) => {
449
+ if (value === void 0 && !field.required)
450
+ return true;
451
+ return jsonLogic.apply(validation.rule, parent);
452
+ }
453
+ );
454
+ }
455
+ function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
456
+ return ({ logic, formValues }) => {
457
+ const { computedAttributes } = fieldParams;
458
+ const attributes = Object.fromEntries(
459
+ Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID)).filter(([, value]) => value !== null)
460
+ );
461
+ return attributes;
462
+ };
463
+ }
464
+ function handleComputedAttribute(logic, formValues, parentID) {
465
+ return ([key, value]) => {
466
+ if (key === "const")
467
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
468
+ if (typeof value === "string") {
469
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
470
+ }
471
+ };
472
+ }
473
+
474
+ // src/yupSchema.js
378
475
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
379
476
  var baseString = string().trim();
380
477
  var todayDateHint = (/* @__PURE__ */ new Date()).toISOString().substring(0, 10);
@@ -497,7 +594,7 @@ var getYupSchema = ({ inputType, ...field }) => {
497
594
  }
498
595
  return yupSchemas[inputType] || yupSchemasToJsonTypes[jsonType];
499
596
  };
500
- function buildYupSchema(field, config) {
597
+ function buildYupSchema(field, config, logic) {
501
598
  const { inputType, jsonType: jsonTypeValue, errorMessage = {}, ...propertyFields } = field;
502
599
  const isCheckboxBoolean = typeof propertyFields.checkboxValue === "boolean";
503
600
  let baseSchema;
@@ -647,6 +744,11 @@ function buildYupSchema(field, config) {
647
744
  if (propertyFields.const) {
648
745
  validators.push(withConst);
649
746
  }
747
+ if (propertyFields.requiredValidations) {
748
+ propertyFields.requiredValidations.forEach(
749
+ (id) => validators.push(yupSchemaWithCustomJSONLogic({ field, id, logic, config }))
750
+ );
751
+ }
650
752
  return flow(validators);
651
753
  }
652
754
  function getNoSortEdges(fields = []) {
@@ -686,8 +788,8 @@ function hasType(type, typeName) {
686
788
  function getField(fieldName, fields) {
687
789
  return fields.find(({ name }) => name === fieldName);
688
790
  }
689
- function validateFieldSchema(field, value) {
690
- const validator = buildYupSchema(field);
791
+ function validateFieldSchema(field, value, logic) {
792
+ const validator = buildYupSchema(field, {}, logic);
691
793
  return validator().isValidSync(value);
692
794
  }
693
795
  function compareFormValueWithSchemaValue(formValue, schemaValue) {
@@ -761,7 +863,7 @@ function getPrefillValues(fields, initialValues = {}) {
761
863
  });
762
864
  return initialValues;
763
865
  }
764
- function updateField(field, requiredFields, node, formValues) {
866
+ function updateField(field, requiredFields, node, formValues, logic, config) {
765
867
  if (!field) {
766
868
  return;
767
869
  }
@@ -783,6 +885,17 @@ function updateField(field, requiredFields, node, formValues) {
783
885
  }
784
886
  }
785
887
  });
888
+ if (field.getComputedAttributes) {
889
+ const computedFieldValues = field.getComputedAttributes({
890
+ field,
891
+ isRequired: fieldIsRequired,
892
+ node,
893
+ formValues,
894
+ config,
895
+ logic
896
+ });
897
+ updateValues(computedFieldValues);
898
+ }
786
899
  if (field.calculateConditionalProperties) {
787
900
  const newFieldValues = field.calculateConditionalProperties(fieldIsRequired, node);
788
901
  updateValues(newFieldValues);
@@ -796,33 +909,46 @@ function updateField(field, requiredFields, node, formValues) {
796
909
  updateValues(newFieldValues);
797
910
  }
798
911
  }
799
- function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */ new Set()) {
912
+ function processNode({
913
+ node,
914
+ formValues,
915
+ formFields,
916
+ accRequired = /* @__PURE__ */ new Set(),
917
+ parentID = "root",
918
+ logic
919
+ }) {
800
920
  const requiredFields = new Set(accRequired);
801
921
  Object.keys(node.properties ?? []).forEach((fieldName) => {
802
922
  const field = getField(fieldName, formFields);
803
- updateField(field, requiredFields, node, formValues);
923
+ updateField(field, requiredFields, node, formValues, logic, { parentID });
804
924
  });
805
925
  node.required?.forEach((fieldName) => {
806
926
  requiredFields.add(fieldName);
807
- updateField(getField(fieldName, formFields), requiredFields, node, formValues);
927
+ updateField(getField(fieldName, formFields), requiredFields, node, formValues, logic, {
928
+ parentID
929
+ });
808
930
  });
809
931
  if (node.if) {
810
- const matchesCondition = checkIfConditionMatches(node, formValues, formFields);
932
+ const matchesCondition = checkIfConditionMatches(node, formValues, formFields, logic);
811
933
  if (matchesCondition && node.then) {
812
- const { required: branchRequired } = processNode(
813
- node.then,
934
+ const { required: branchRequired } = processNode({
935
+ node: node.then,
814
936
  formValues,
815
937
  formFields,
816
- requiredFields
817
- );
938
+ accRequired: requiredFields,
939
+ parentID,
940
+ logic
941
+ });
818
942
  branchRequired.forEach((field) => requiredFields.add(field));
819
943
  } else if (node.else) {
820
- const { required: branchRequired } = processNode(
821
- node.else,
944
+ const { required: branchRequired } = processNode({
945
+ node: node.else,
822
946
  formValues,
823
947
  formFields,
824
- requiredFields
825
- );
948
+ accRequired: requiredFields,
949
+ parentID,
950
+ logic
951
+ });
826
952
  branchRequired.forEach((field) => requiredFields.add(field));
827
953
  }
828
954
  }
@@ -834,12 +960,21 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
834
960
  node.anyOf.forEach(({ required = [] }) => {
835
961
  required.forEach((fieldName) => {
836
962
  const field = getField(fieldName, formFields);
837
- updateField(field, requiredFields, node, formValues);
963
+ updateField(field, requiredFields, node, formValues, logic, { parentID });
838
964
  });
839
965
  });
840
966
  }
841
967
  if (node.allOf) {
842
- node.allOf.map((allOfNode) => processNode(allOfNode, formValues, formFields, requiredFields)).forEach(({ required: allOfItemRequired }) => {
968
+ node.allOf.map(
969
+ (allOfNode) => processNode({
970
+ node: allOfNode,
971
+ formValues,
972
+ formFields,
973
+ accRequired: requiredFields,
974
+ parentID,
975
+ logic
976
+ })
977
+ ).forEach(({ required: allOfItemRequired }) => {
843
978
  allOfItemRequired.forEach(requiredFields.add, requiredFields);
844
979
  });
845
980
  }
@@ -847,7 +982,13 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
847
982
  Object.entries(node.properties).forEach(([name, nestedNode]) => {
848
983
  const inputType = getInputType(nestedNode);
849
984
  if (inputType === supportedTypes.FIELDSET) {
850
- processNode(nestedNode, formValues[name] || {}, getField(name, formFields).fields);
985
+ processNode({
986
+ node: nestedNode,
987
+ formValues: formValues[name] || {},
988
+ formFields: getField(name, formFields).fields,
989
+ parentID: name,
990
+ logic
991
+ });
851
992
  }
852
993
  });
853
994
  }
@@ -865,11 +1006,11 @@ function clearValuesIfNotVisible(fields, formValues) {
865
1006
  }
866
1007
  });
867
1008
  }
868
- function updateFieldsProperties(fields, formValues, jsonSchema) {
1009
+ function updateFieldsProperties(fields, formValues, jsonSchema, logic) {
869
1010
  if (!jsonSchema?.properties) {
870
1011
  return;
871
1012
  }
872
- processNode(jsonSchema, formValues, fields);
1013
+ processNode({ node: jsonSchema, formValues, formFields: fields, logic });
873
1014
  clearValuesIfNotVisible(fields, formValues);
874
1015
  }
875
1016
  var notNullOption = (opt) => opt.const !== null;
@@ -912,12 +1053,16 @@ function extractParametersFromNode(schemaNode) {
912
1053
  }
913
1054
  const presentation = pickXKey(schemaNode, "presentation") ?? {};
914
1055
  const errorMessage = pickXKey(schemaNode, "errorMessage") ?? {};
1056
+ const requiredValidations = schemaNode["x-jsf-logic-validations"];
1057
+ const computedAttributes = schemaNode["x-jsf-logic-computedAttrs"];
1058
+ const decoratedComputedAttributes = getDecoratedComputedAttributes(computedAttributes);
915
1059
  const node = omit(schemaNode, ["x-jsf-presentation", "presentation"]);
916
1060
  const description = presentation?.description || node.description;
917
1061
  const statementDescription = containsHTML(presentation.statement?.description) ? wrapWithSpan(presentation.statement.description, { class: "jsf-statement" }) : presentation.statement?.description;
918
1062
  return omitBy(
919
1063
  {
920
1064
  const: node.const,
1065
+ ...node.const && node.default ? { value: node.const } : {},
921
1066
  label: node.title,
922
1067
  readOnly: node.readOnly,
923
1068
  ...node.deprecated && {
@@ -952,6 +1097,8 @@ function extractParametersFromNode(schemaNode) {
952
1097
  },
953
1098
  // Handle [name].presentation
954
1099
  ...presentation,
1100
+ requiredValidations,
1101
+ computedAttributes: decoratedComputedAttributes,
955
1102
  description: containsHTML(description) ? wrapWithSpan(description, {
956
1103
  class: "jsf-description"
957
1104
  }) : description,
@@ -988,8 +1135,8 @@ function yupToFormErrors(yupError) {
988
1135
  }
989
1136
  return errors;
990
1137
  }
991
- var handleValuesChange = (fields, jsonSchema, config) => (values) => {
992
- updateFieldsProperties(fields, values, jsonSchema);
1138
+ var handleValuesChange = (fields, jsonSchema, config, logic) => (values) => {
1139
+ updateFieldsProperties(fields, values, jsonSchema, logic);
993
1140
  const lazySchema = lazy(() => buildCompleteYupSchema(fields, config));
994
1141
  let errors;
995
1142
  try {
@@ -1008,6 +1155,12 @@ var handleValuesChange = (fields, jsonSchema, config) => (values) => {
1008
1155
  formErrors: yupToFormErrors(errors)
1009
1156
  };
1010
1157
  };
1158
+ function getDecoratedComputedAttributes(computedAttributes) {
1159
+ return {
1160
+ ...computedAttributes ?? {},
1161
+ ...computedAttributes?.const && computedAttributes?.default ? { value: computedAttributes.const } : {}
1162
+ };
1163
+ }
1011
1164
 
1012
1165
  // src/calculateConditionalProperties.js
1013
1166
  function isFieldRequired(node, field) {
@@ -1209,6 +1362,9 @@ function applyFieldsDependencies(fieldsParameters, node) {
1209
1362
  applyFieldsDependencies(fieldsParameters, condition);
1210
1363
  });
1211
1364
  }
1365
+ if (node?.["x-jsf-logic"]) {
1366
+ applyFieldsDependencies(fieldsParameters, node["x-jsf-logic"]);
1367
+ }
1212
1368
  }
1213
1369
  function getCustomPropertiesForField(fieldParams, config) {
1214
1370
  return config?.customProperties?.[fieldParams.name];
@@ -1220,15 +1376,16 @@ function getComposeFunctionForField(fieldParams, hasCustomizations) {
1220
1376
  }
1221
1377
  return composeFn;
1222
1378
  }
1223
- function buildField(fieldParams, config, scopedJsonSchema) {
1379
+ function buildField(fieldParams, config, scopedJsonSchema, logic) {
1224
1380
  const customProperties = getCustomPropertiesForField(fieldParams, config);
1225
1381
  const composeFn = getComposeFunctionForField(fieldParams, !!customProperties);
1226
- const yupSchema = buildYupSchema(fieldParams, config);
1382
+ const yupSchema = buildYupSchema(fieldParams, config, logic);
1227
1383
  const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties);
1228
1384
  const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties(
1229
1385
  fieldParams,
1230
1386
  customProperties
1231
1387
  );
1388
+ const getComputedAttributes = Object.keys(fieldParams.computedAttributes).length > 0 && calculateComputedAttributes(fieldParams, config);
1232
1389
  const hasCustomValidations = !!customProperties && size(pick2(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS)) > 0;
1233
1390
  const finalFieldParams = {
1234
1391
  // invalid attribute cleanup
@@ -1241,6 +1398,7 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1241
1398
  ...hasCustomValidations && {
1242
1399
  calculateCustomValidationProperties: calculateCustomValidationPropertiesClosure
1243
1400
  },
1401
+ ...getComputedAttributes && { getComputedAttributes },
1244
1402
  // field customization properties
1245
1403
  ...customProperties && { fieldCustomization: customProperties },
1246
1404
  // base schema
@@ -1249,7 +1407,7 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1249
1407
  };
1250
1408
  return composeFn(finalFieldParams);
1251
1409
  }
1252
- function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1410
+ function getFieldsFromJSONSchema(scopedJsonSchema, config, logic) {
1253
1411
  if (!scopedJsonSchema) {
1254
1412
  return [];
1255
1413
  }
@@ -1273,11 +1431,11 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1273
1431
  fields: () => groupArrayFields,
1274
1432
  addFieldText: fieldParams.addFieldText
1275
1433
  };
1276
- buildField(fieldParams, config, scopedJsonSchema).forEach((groupField) => {
1434
+ buildField(fieldParams, config, scopedJsonSchema, logic).forEach((groupField) => {
1277
1435
  fields.push(groupField);
1278
1436
  });
1279
1437
  } else {
1280
- fields.push(buildField(fieldParams, config, scopedJsonSchema));
1438
+ fields.push(buildField(fieldParams, config, scopedJsonSchema, logic));
1281
1439
  }
1282
1440
  });
1283
1441
  return fields;
@@ -1288,9 +1446,15 @@ function createHeadlessForm(jsonSchema, customConfig = {}) {
1288
1446
  ...customConfig
1289
1447
  };
1290
1448
  try {
1291
- const fields = getFieldsFromJSONSchema(jsonSchema, config);
1292
- const handleValidation = handleValuesChange(fields, jsonSchema, config);
1293
- updateFieldsProperties(fields, getPrefillValues(fields, config.initialValues), jsonSchema);
1449
+ const logic = createValidationChecker(jsonSchema);
1450
+ const fields = getFieldsFromJSONSchema(jsonSchema, config, logic);
1451
+ const handleValidation = handleValuesChange(fields, jsonSchema, config, logic);
1452
+ updateFieldsProperties(
1453
+ fields,
1454
+ getPrefillValues(fields, config.initialValues),
1455
+ jsonSchema,
1456
+ logic
1457
+ );
1294
1458
  return {
1295
1459
  fields,
1296
1460
  handleValidation,