@remoteoss/json-schema-form 0.5.0-dev.20230810172154 → 0.5.0-dev.20230901130231

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.5.0-dev.20230810172154
5
- Generated: Thu, 10 Aug 2023 17:22:35 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.5.0-dev.20230901130231
5
+ Generated: Fri, 01 Sep 2023 13:03:04 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -55,6 +55,13 @@ function convertDiskSizeFromTo(from, to) {
55
55
  return value * Math.pow(1024, units.indexOf(from.toLowerCase())) / Math.pow(1024, units.indexOf(to.toLowerCase()));
56
56
  };
57
57
  }
58
+ function containsHTML(str = "") {
59
+ return /<[a-z][\s\S]*>/i.test(str);
60
+ }
61
+ function wrapWithSpan(html, properties = {}) {
62
+ const attributes = Object.entries(properties).reduce((acc, [key, value]) => `${acc}${key}="${value}" `, "").trim();
63
+ return `<span ${attributes}>${html}</span>`;
64
+ }
58
65
  function hasProperty(object2, propertyName) {
59
66
  return Object.prototype.hasOwnProperty.call(object2, propertyName);
60
67
  }
@@ -368,6 +375,103 @@ import flow from "lodash/flow";
368
375
  import noop from "lodash/noop";
369
376
  import { randexp } from "randexp";
370
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
371
475
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
372
476
  var baseString = string().trim();
373
477
  var todayDateHint = (/* @__PURE__ */ new Date()).toISOString().substring(0, 10);
@@ -490,7 +594,7 @@ var getYupSchema = ({ inputType, ...field }) => {
490
594
  }
491
595
  return yupSchemas[inputType] || yupSchemasToJsonTypes[jsonType];
492
596
  };
493
- function buildYupSchema(field, config) {
597
+ function buildYupSchema(field, config, logic) {
494
598
  const { inputType, jsonType: jsonTypeValue, errorMessage = {}, ...propertyFields } = field;
495
599
  const isCheckboxBoolean = typeof propertyFields.checkboxValue === "boolean";
496
600
  let baseSchema;
@@ -563,6 +667,13 @@ function buildYupSchema(field, config) {
563
667
  }) : true
564
668
  );
565
669
  }
670
+ function withConst(yupSchema) {
671
+ return yupSchema.test(
672
+ "isConst",
673
+ errorMessage.const ?? errorMessageFromConfig.const ?? `The only accepted value is ${propertyFields.const}.`,
674
+ (value) => propertyFields.required === false && value === void 0 || value === null || value === propertyFields.const
675
+ );
676
+ }
566
677
  function withBaseSchema() {
567
678
  const customErrorMsg = errorMessage.type || errorMessageFromConfig.type;
568
679
  if (customErrorMsg) {
@@ -630,6 +741,14 @@ function buildYupSchema(field, config) {
630
741
  if (propertyFields.accept) {
631
742
  validators.push(withFileFormat);
632
743
  }
744
+ if (propertyFields.const) {
745
+ validators.push(withConst);
746
+ }
747
+ if (propertyFields.requiredValidations) {
748
+ propertyFields.requiredValidations.forEach(
749
+ (id) => validators.push(yupSchemaWithCustomJSONLogic({ field, id, logic, config }))
750
+ );
751
+ }
633
752
  return flow(validators);
634
753
  }
635
754
  function getNoSortEdges(fields = []) {
@@ -669,8 +788,8 @@ function hasType(type, typeName) {
669
788
  function getField(fieldName, fields) {
670
789
  return fields.find(({ name }) => name === fieldName);
671
790
  }
672
- function validateFieldSchema(field, value) {
673
- const validator = buildYupSchema(field);
791
+ function validateFieldSchema(field, value, logic) {
792
+ const validator = buildYupSchema(field, {}, logic);
674
793
  return validator().isValidSync(value);
675
794
  }
676
795
  function compareFormValueWithSchemaValue(formValue, schemaValue) {
@@ -706,7 +825,16 @@ function getPrefillSubFieldValues(field, defaultValues, parentFieldKeyPath) {
706
825
  initialValue[field.name] = subFieldValues;
707
826
  }
708
827
  } else {
709
- initialValue = getPrefillValues([field], initialValue);
828
+ if (typeof initialValue !== "object") {
829
+ console.warn(
830
+ `Field "${parentFieldKeyPath}"'s value is "${initialValue}", but should be type object.`
831
+ );
832
+ initialValue = getPrefillValues([field], {
833
+ // TODO nested fieldsets are not handled
834
+ });
835
+ } else {
836
+ initialValue = getPrefillValues([field], initialValue);
837
+ }
710
838
  }
711
839
  return initialValue;
712
840
  }
@@ -735,7 +863,7 @@ function getPrefillValues(fields, initialValues = {}) {
735
863
  });
736
864
  return initialValues;
737
865
  }
738
- function updateField(field, requiredFields, node, formValues) {
866
+ function updateField(field, requiredFields, node, formValues, logic, config) {
739
867
  if (!field) {
740
868
  return;
741
869
  }
@@ -757,6 +885,17 @@ function updateField(field, requiredFields, node, formValues) {
757
885
  }
758
886
  }
759
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
+ }
760
899
  if (field.calculateConditionalProperties) {
761
900
  const newFieldValues = field.calculateConditionalProperties(fieldIsRequired, node);
762
901
  updateValues(newFieldValues);
@@ -770,33 +909,46 @@ function updateField(field, requiredFields, node, formValues) {
770
909
  updateValues(newFieldValues);
771
910
  }
772
911
  }
773
- 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
+ }) {
774
920
  const requiredFields = new Set(accRequired);
775
921
  Object.keys(node.properties ?? []).forEach((fieldName) => {
776
922
  const field = getField(fieldName, formFields);
777
- updateField(field, requiredFields, node, formValues);
923
+ updateField(field, requiredFields, node, formValues, logic, { parentID });
778
924
  });
779
925
  node.required?.forEach((fieldName) => {
780
926
  requiredFields.add(fieldName);
781
- updateField(getField(fieldName, formFields), requiredFields, node, formValues);
927
+ updateField(getField(fieldName, formFields), requiredFields, node, formValues, logic, {
928
+ parentID
929
+ });
782
930
  });
783
931
  if (node.if) {
784
- const matchesCondition = checkIfConditionMatches(node, formValues, formFields);
932
+ const matchesCondition = checkIfConditionMatches(node, formValues, formFields, logic);
785
933
  if (matchesCondition && node.then) {
786
- const { required: branchRequired } = processNode(
787
- node.then,
934
+ const { required: branchRequired } = processNode({
935
+ node: node.then,
788
936
  formValues,
789
937
  formFields,
790
- requiredFields
791
- );
938
+ accRequired: requiredFields,
939
+ parentID,
940
+ logic
941
+ });
792
942
  branchRequired.forEach((field) => requiredFields.add(field));
793
943
  } else if (node.else) {
794
- const { required: branchRequired } = processNode(
795
- node.else,
944
+ const { required: branchRequired } = processNode({
945
+ node: node.else,
796
946
  formValues,
797
947
  formFields,
798
- requiredFields
799
- );
948
+ accRequired: requiredFields,
949
+ parentID,
950
+ logic
951
+ });
800
952
  branchRequired.forEach((field) => requiredFields.add(field));
801
953
  }
802
954
  }
@@ -808,12 +960,21 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
808
960
  node.anyOf.forEach(({ required = [] }) => {
809
961
  required.forEach((fieldName) => {
810
962
  const field = getField(fieldName, formFields);
811
- updateField(field, requiredFields, node, formValues);
963
+ updateField(field, requiredFields, node, formValues, logic, { parentID });
812
964
  });
813
965
  });
814
966
  }
815
967
  if (node.allOf) {
816
- 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 }) => {
817
978
  allOfItemRequired.forEach(requiredFields.add, requiredFields);
818
979
  });
819
980
  }
@@ -821,7 +982,13 @@ function processNode(node, formValues, formFields, accRequired = /* @__PURE__ */
821
982
  Object.entries(node.properties).forEach(([name, nestedNode]) => {
822
983
  const inputType = getInputType(nestedNode);
823
984
  if (inputType === supportedTypes.FIELDSET) {
824
- 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
+ });
825
992
  }
826
993
  });
827
994
  }
@@ -839,11 +1006,11 @@ function clearValuesIfNotVisible(fields, formValues) {
839
1006
  }
840
1007
  });
841
1008
  }
842
- function updateFieldsProperties(fields, formValues, jsonSchema) {
1009
+ function updateFieldsProperties(fields, formValues, jsonSchema, logic) {
843
1010
  if (!jsonSchema?.properties) {
844
1011
  return;
845
1012
  }
846
- processNode(jsonSchema, formValues, fields);
1013
+ processNode({ node: jsonSchema, formValues, formFields: fields, logic });
847
1014
  clearValuesIfNotVisible(fields, formValues);
848
1015
  }
849
1016
  var notNullOption = (opt) => opt.const !== null;
@@ -886,11 +1053,15 @@ function extractParametersFromNode(schemaNode) {
886
1053
  }
887
1054
  const presentation = pickXKey(schemaNode, "presentation") ?? {};
888
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);
889
1059
  const node = omit(schemaNode, ["x-jsf-presentation", "presentation"]);
890
1060
  const description = presentation?.description || node.description;
891
- const statementDescription = presentation.statement?.description;
1061
+ const statementDescription = containsHTML(presentation.statement?.description) ? wrapWithSpan(presentation.statement.description, { class: "jsf-statement" }) : presentation.statement?.description;
892
1062
  return omitBy(
893
1063
  {
1064
+ const: node.const,
894
1065
  label: node.title,
895
1066
  readOnly: node.readOnly,
896
1067
  ...node.deprecated && {
@@ -925,8 +1096,12 @@ function extractParametersFromNode(schemaNode) {
925
1096
  },
926
1097
  // Handle [name].presentation
927
1098
  ...presentation,
928
- description,
929
- extra: presentation.extra,
1099
+ requiredValidations,
1100
+ computedAttributes: decoratedComputedAttributes,
1101
+ description: containsHTML(description) ? wrapWithSpan(description, {
1102
+ class: "jsf-description"
1103
+ }) : description,
1104
+ extra: containsHTML(presentation.extra) ? wrapWithSpan(presentation.extra, { class: "jsf-extra" }) : presentation.extra,
930
1105
  statement: presentation.statement && {
931
1106
  ...presentation.statement,
932
1107
  description: statementDescription
@@ -959,8 +1134,8 @@ function yupToFormErrors(yupError) {
959
1134
  }
960
1135
  return errors;
961
1136
  }
962
- var handleValuesChange = (fields, jsonSchema, config) => (values) => {
963
- updateFieldsProperties(fields, values, jsonSchema);
1137
+ var handleValuesChange = (fields, jsonSchema, config, logic) => (values) => {
1138
+ updateFieldsProperties(fields, values, jsonSchema, logic);
964
1139
  const lazySchema = lazy(() => buildCompleteYupSchema(fields, config));
965
1140
  let errors;
966
1141
  try {
@@ -979,6 +1154,12 @@ var handleValuesChange = (fields, jsonSchema, config) => (values) => {
979
1154
  formErrors: yupToFormErrors(errors)
980
1155
  };
981
1156
  };
1157
+ function getDecoratedComputedAttributes(computedAttributes) {
1158
+ return {
1159
+ ...computedAttributes ?? {},
1160
+ ...computedAttributes?.const && computedAttributes?.default ? { value: computedAttributes.const } : {}
1161
+ };
1162
+ }
982
1163
 
983
1164
  // src/calculateConditionalProperties.js
984
1165
  function isFieldRequired(node, field) {
@@ -1180,6 +1361,9 @@ function applyFieldsDependencies(fieldsParameters, node) {
1180
1361
  applyFieldsDependencies(fieldsParameters, condition);
1181
1362
  });
1182
1363
  }
1364
+ if (node?.["x-jsf-logic"]) {
1365
+ applyFieldsDependencies(fieldsParameters, node["x-jsf-logic"]);
1366
+ }
1183
1367
  }
1184
1368
  function getCustomPropertiesForField(fieldParams, config) {
1185
1369
  return config?.customProperties?.[fieldParams.name];
@@ -1191,15 +1375,16 @@ function getComposeFunctionForField(fieldParams, hasCustomizations) {
1191
1375
  }
1192
1376
  return composeFn;
1193
1377
  }
1194
- function buildField(fieldParams, config, scopedJsonSchema) {
1378
+ function buildField(fieldParams, config, scopedJsonSchema, logic) {
1195
1379
  const customProperties = getCustomPropertiesForField(fieldParams, config);
1196
1380
  const composeFn = getComposeFunctionForField(fieldParams, !!customProperties);
1197
- const yupSchema = buildYupSchema(fieldParams, config);
1381
+ const yupSchema = buildYupSchema(fieldParams, config, logic);
1198
1382
  const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties);
1199
1383
  const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties(
1200
1384
  fieldParams,
1201
1385
  customProperties
1202
1386
  );
1387
+ const getComputedAttributes = Object.keys(fieldParams.computedAttributes).length > 0 && calculateComputedAttributes(fieldParams, config);
1203
1388
  const hasCustomValidations = !!customProperties && size(pick2(customProperties, SUPPORTED_CUSTOM_VALIDATION_FIELD_PARAMS)) > 0;
1204
1389
  const finalFieldParams = {
1205
1390
  // invalid attribute cleanup
@@ -1212,6 +1397,7 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1212
1397
  ...hasCustomValidations && {
1213
1398
  calculateCustomValidationProperties: calculateCustomValidationPropertiesClosure
1214
1399
  },
1400
+ ...getComputedAttributes && { getComputedAttributes },
1215
1401
  // field customization properties
1216
1402
  ...customProperties && { fieldCustomization: customProperties },
1217
1403
  // base schema
@@ -1220,7 +1406,7 @@ function buildField(fieldParams, config, scopedJsonSchema) {
1220
1406
  };
1221
1407
  return composeFn(finalFieldParams);
1222
1408
  }
1223
- function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1409
+ function getFieldsFromJSONSchema(scopedJsonSchema, config, logic) {
1224
1410
  if (!scopedJsonSchema) {
1225
1411
  return [];
1226
1412
  }
@@ -1244,11 +1430,11 @@ function getFieldsFromJSONSchema(scopedJsonSchema, config) {
1244
1430
  fields: () => groupArrayFields,
1245
1431
  addFieldText: fieldParams.addFieldText
1246
1432
  };
1247
- buildField(fieldParams, config, scopedJsonSchema).forEach((groupField) => {
1433
+ buildField(fieldParams, config, scopedJsonSchema, logic).forEach((groupField) => {
1248
1434
  fields.push(groupField);
1249
1435
  });
1250
1436
  } else {
1251
- fields.push(buildField(fieldParams, config, scopedJsonSchema));
1437
+ fields.push(buildField(fieldParams, config, scopedJsonSchema, logic));
1252
1438
  }
1253
1439
  });
1254
1440
  return fields;
@@ -1259,9 +1445,15 @@ function createHeadlessForm(jsonSchema, customConfig = {}) {
1259
1445
  ...customConfig
1260
1446
  };
1261
1447
  try {
1262
- const fields = getFieldsFromJSONSchema(jsonSchema, config);
1263
- const handleValidation = handleValuesChange(fields, jsonSchema, config);
1264
- updateFieldsProperties(fields, getPrefillValues(fields, config.initialValues), jsonSchema);
1448
+ const logic = createValidationChecker(jsonSchema);
1449
+ const fields = getFieldsFromJSONSchema(jsonSchema, config, logic);
1450
+ const handleValidation = handleValuesChange(fields, jsonSchema, config, logic);
1451
+ updateFieldsProperties(
1452
+ fields,
1453
+ getPrefillValues(fields, config.initialValues),
1454
+ jsonSchema,
1455
+ logic
1456
+ );
1265
1457
  return {
1266
1458
  fields,
1267
1459
  handleValidation,