@remoteoss/json-schema-form 0.6.4-beta.0 → 0.6.5-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.6.4-beta.0
5
- Generated: Fri, 15 Sep 2023 12:36:13 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.6.5-beta.0
5
+ Generated: Mon, 18 Sep 2023 10:09:01 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 checkIfConditionMatchesProperties(node, formValues, formFields, logic) {
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".
@@ -91,10 +91,11 @@ function checkIfConditionMatches(node, formValues, formFields) {
91
91
  return currentProperty.enum.includes(value);
92
92
  }
93
93
  if (currentProperty.properties) {
94
- return checkIfConditionMatches(
94
+ return checkIfConditionMatchesProperties(
95
95
  { if: currentProperty },
96
96
  formValues[name],
97
- getField(name, formFields).fields
97
+ getField(name, formFields).fields,
98
+ logic
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, logic, parentID) {
115
+ const validationsMatch = Object.entries(node.if.validations ?? {}).every(([name, property]) => {
116
+ const currentValue = logic.getScope(parentID).applyValidationRuleInCondition(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 = logic.getScope(parentID).applyComputedValueRuleInCondition(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,261 +388,14 @@ 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";
376
397
  import { randexp } from "randexp";
377
398
  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
- const sampleEmptyObject = buildSampleEmptyObject(schema);
385
- scopes.set(key, createValidationsScope(jsonSchema));
386
- Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
387
- if (property.type === "array") {
388
- createScopes(property.items, `${key2}[]`);
389
- } else {
390
- createScopes(property, key2);
391
- }
392
- });
393
- validateInlineRules(jsonSchema, sampleEmptyObject);
394
- }
395
- createScopes(schema);
396
- return {
397
- scopes,
398
- getScope(name = "root") {
399
- return scopes.get(name);
400
- }
401
- };
402
- }
403
- function createValidationsScope(schema) {
404
- const validationMap = /* @__PURE__ */ new Map();
405
- const computedValuesMap = /* @__PURE__ */ new Map();
406
- const logic = schema?.["x-jsf-logic"] ?? {
407
- validations: {},
408
- computedValues: {}
409
- };
410
- const validations = Object.entries(logic.validations ?? {});
411
- const computedValues = Object.entries(logic.computedValues ?? {});
412
- const sampleEmptyObject = buildSampleEmptyObject(schema);
413
- validations.forEach(([id, validation]) => {
414
- if (!validation.rule) {
415
- throw Error(`[json-schema-form] json-logic error: Validation "${id}" has missing rule.`);
416
- }
417
- checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
418
- validationMap.set(id, validation);
419
- });
420
- computedValues.forEach(([id, computedValue]) => {
421
- if (!computedValue.rule) {
422
- throw Error(`[json-schema-form] json-logic error: Computed value "${id}" has missing rule.`);
423
- }
424
- checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
425
- computedValuesMap.set(id, computedValue);
426
- });
427
- function validate(rule, values) {
428
- return jsonLogic.apply(rule, replaceUndefinedValuesWithNulls(values));
429
- }
430
- return {
431
- validationMap,
432
- computedValuesMap,
433
- validate,
434
- applyValidationRuleInCondition(id, values) {
435
- const validation = validationMap.get(id);
436
- return validate(validation.rule, values);
437
- },
438
- applyComputedValueInField(id, values, fieldName) {
439
- const validation = computedValuesMap.get(id);
440
- if (validation === void 0) {
441
- throw Error(
442
- `[json-schema-form] json-logic error: Computed value "${id}" doesn't exist in field "${fieldName}".`
443
- );
444
- }
445
- return validate(validation.rule, values);
446
- },
447
- applyComputedValueRuleInCondition(id, values) {
448
- const validation = computedValuesMap.get(id);
449
- return validate(validation.rule, values);
450
- }
451
- };
452
- }
453
- function replaceUndefinedValuesWithNulls(values = {}) {
454
- return Object.entries(values).reduce((prev, [key, value]) => {
455
- return { ...prev, [key]: value === void 0 ? null : value };
456
- }, {});
457
- }
458
- function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
459
- const { parentID = "root" } = config;
460
- const validation = logic.getScope(parentID).validationMap.get(id);
461
- if (validation === void 0) {
462
- throw Error(
463
- `[json-schema-form] json-logic error: "${field.name}" required validation "${id}" doesn't exist.`
464
- );
465
- }
466
- return (yupSchema) => yupSchema.test(
467
- `${field.name}-validation-${id}`,
468
- validation?.errorMessage ?? "This field is invalid.",
469
- (value, { parent }) => {
470
- if (value === void 0 && !field.required)
471
- return true;
472
- return jsonLogic.apply(validation.rule, parent);
473
- }
474
- );
475
- }
476
- var HANDLEBARS_REGEX = /\{\{([^{}]+)\}\}/g;
477
- function replaceHandlebarsTemplates({
478
- value: toReplace,
479
- logic,
480
- formValues,
481
- parentID,
482
- name: fieldName
483
- }) {
484
- if (typeof toReplace === "string") {
485
- return toReplace.replace(HANDLEBARS_REGEX, (match, key) => {
486
- return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
487
- });
488
- } else if (typeof toReplace === "object") {
489
- const { value, ...rules } = toReplace;
490
- if (Object.keys(rules).length > 1 && !value) {
491
- throw Error("Cannot define multiple rules without a template string with key `value`.");
492
- }
493
- const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
494
- const computedValue = logic.getScope(parentID).validate(rule, formValues);
495
- return prev.replaceAll(`{{${key}}}`, computedValue);
496
- }, value);
497
- return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
498
- return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
499
- });
500
- }
501
- return toReplace;
502
- }
503
- function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
504
- return ({ logic, isRequired, config, formValues }) => {
505
- const { name, computedAttributes } = fieldParams;
506
- const attributes = Object.fromEntries(
507
- Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
508
- );
509
- return {
510
- ...attributes,
511
- schema: buildYupSchema(
512
- { ...fieldParams, ...attributes, required: isRequired },
513
- config,
514
- logic
515
- )
516
- };
517
- };
518
- }
519
- function handleComputedAttribute(logic, formValues, parentID, name) {
520
- return ([key, value]) => {
521
- switch (key) {
522
- case "description":
523
- return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
524
- case "title":
525
- return ["label", replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
526
- case "x-jsf-errorMessage":
527
- return [
528
- "errorMessage",
529
- handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
530
- ];
531
- case "x-jsf-presentation": {
532
- if (value.statement) {
533
- return [
534
- "statement",
535
- handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
536
- ];
537
- }
538
- return [
539
- key,
540
- handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
541
- ];
542
- }
543
- case "const":
544
- default: {
545
- if (typeof value === "object" && value.rule) {
546
- return [key, logic.getScope(parentID).validate(value.rule, formValues)];
547
- }
548
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues, name)];
549
- }
550
- }
551
- };
552
- }
553
- function handleNestedObjectForComputedValues(values, formValues, parentID, logic, name) {
554
- return Object.fromEntries(
555
- Object.entries(values).map(([key, value]) => {
556
- return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
557
- })
558
- );
559
- }
560
- function buildSampleEmptyObject(schema = {}) {
561
- const sample = {};
562
- if (typeof schema !== "object" || !schema.properties) {
563
- return schema;
564
- }
565
- for (const key in schema.properties) {
566
- if (schema.properties[key].type === "object") {
567
- sample[key] = buildSampleEmptyObject(schema.properties[key]);
568
- } else if (schema.properties[key].type === "array") {
569
- const itemSchema = schema.properties[key].items;
570
- sample[key] = buildSampleEmptyObject(itemSchema);
571
- } else {
572
- sample[key] = true;
573
- }
574
- }
575
- return sample;
576
- }
577
- function validateInlineRules(jsonSchema, sampleEmptyObject) {
578
- const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
579
- Object.entries(properties).filter(([, property]) => property["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property]) => {
580
- Object.entries(property["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
581
- Object.values(item).forEach((rule) => {
582
- checkRuleIntegrity(
583
- rule,
584
- fieldName,
585
- sampleEmptyObject,
586
- (item2) => `[json-schema-form] json-logic error: fieldName "${item2.var}" doesn't exist in field "${fieldName}.x-jsf-logic-computedAttrs.${key}".`
587
- );
588
- });
589
- });
590
- });
591
- }
592
- function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`) {
593
- Object.entries(rule ?? {}).map(([operator, subRule]) => {
594
- if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
595
- return;
596
- throwIfUnknownOperator(operator, subRule, id);
597
- subRule.map((item) => {
598
- const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
599
- if (isVar) {
600
- const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, data);
601
- if (exists === null) {
602
- throw Error(errorMessage(item));
603
- }
604
- } else {
605
- checkRuleIntegrity(item, id, data);
606
- }
607
- });
608
- });
609
- }
610
- function throwIfUnknownOperator(operator, subRule, id) {
611
- try {
612
- jsonLogic.apply({ [operator]: subRule });
613
- } catch (e) {
614
- if (e.message === `Unrecognized operation ${operator}`) {
615
- throw Error(
616
- `[json-schema-form] json-logic error: in "${id}" rule there is an unknown operator "${operator}".`
617
- );
618
- }
619
- }
620
- }
621
- var regexToGetIndices = /\.\d+\./g;
622
- function removeIndicesFromPath(path) {
623
- const intermediatePath = path.replace(regexToGetIndices, ".");
624
- return intermediatePath.replace(/\.\d+$/, "");
625
- }
626
-
627
- // src/yupSchema.js
628
399
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
629
400
  var baseString = string().trim();
630
401
  var todayDateHint = (/* @__PURE__ */ new Date()).toISOString().substring(0, 10);
@@ -820,118 +591,414 @@ function buildYupSchema(field, config, logic) {
820
591
  }) : true
821
592
  );
822
593
  }
823
- function withConst(yupSchema) {
824
- return yupSchema.test(
825
- "isConst",
826
- errorMessage.const ?? errorMessageFromConfig.const ?? `The only accepted value is ${propertyFields.const}.`,
827
- (value) => propertyFields.required === false && value === void 0 || value === null || value === propertyFields.const
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
+ }
601
+ function withBaseSchema() {
602
+ const customErrorMsg = errorMessage.type || errorMessageFromConfig.type;
603
+ if (customErrorMsg) {
604
+ return baseSchema.typeError(customErrorMsg);
605
+ }
606
+ return baseSchema;
607
+ }
608
+ function buildFieldSetSchema(innerFields) {
609
+ const fieldSetShape = {};
610
+ innerFields.forEach((fieldSetfield) => {
611
+ if (fieldSetfield.fields) {
612
+ fieldSetShape[fieldSetfield.name] = object().shape(
613
+ buildFieldSetSchema(fieldSetfield.fields)
614
+ );
615
+ } else {
616
+ fieldSetShape[fieldSetfield.name] = buildYupSchema(
617
+ {
618
+ ...fieldSetfield,
619
+ inputType: fieldSetfield.type
620
+ },
621
+ config
622
+ )();
623
+ }
624
+ });
625
+ return fieldSetShape;
626
+ }
627
+ function buildGroupArraySchema() {
628
+ return object().shape(
629
+ propertyFields.nthFieldGroup.fields().reduce(
630
+ (schema, groupArrayField) => ({
631
+ ...schema,
632
+ [groupArrayField.name]: buildYupSchema(groupArrayField, config)()
633
+ }),
634
+ {}
635
+ )
636
+ );
637
+ }
638
+ const validators = [withBaseSchema];
639
+ if (inputType === supportedTypes.GROUP_ARRAY) {
640
+ validators[0] = () => withBaseSchema().of(buildGroupArraySchema());
641
+ } else if (inputType === supportedTypes.FIELDSET) {
642
+ validators[0] = () => withBaseSchema().shape(buildFieldSetSchema(propertyFields.fields));
643
+ }
644
+ if (propertyFields.required) {
645
+ validators.push(withRequired);
646
+ }
647
+ if (typeof propertyFields.minimum !== "undefined") {
648
+ validators.push(withMin);
649
+ }
650
+ if (typeof propertyFields.minLength !== "undefined") {
651
+ validators.push(withMinLength);
652
+ }
653
+ if (propertyFields.maximum !== void 0) {
654
+ validators.push(withMax);
655
+ }
656
+ if (propertyFields.maxLength) {
657
+ validators.push(withMaxLength);
658
+ }
659
+ if (propertyFields.pattern) {
660
+ validators.push(withMatches);
661
+ }
662
+ if (propertyFields.maxFileSize) {
663
+ validators.push(withMaxFileSize);
664
+ }
665
+ if (propertyFields.accept) {
666
+ validators.push(withFileFormat);
667
+ }
668
+ if (propertyFields.const) {
669
+ validators.push(withConst);
670
+ }
671
+ if (propertyFields.jsonLogicValidations) {
672
+ propertyFields.jsonLogicValidations.forEach(
673
+ (id) => validators.push(yupSchemaWithCustomJSONLogic({ field, id, logic, config }))
674
+ );
675
+ }
676
+ return flow(validators);
677
+ }
678
+ function getNoSortEdges(fields = []) {
679
+ return fields.reduce((list, field) => {
680
+ if (field.noSortEdges) {
681
+ list.push(field.name);
682
+ }
683
+ return list;
684
+ }, []);
685
+ }
686
+ function getSchema(fields = [], config) {
687
+ const newSchema = {};
688
+ fields.forEach((field) => {
689
+ if (field.schema) {
690
+ if (field.name) {
691
+ if (field.inputType === supportedTypes.FIELDSET) {
692
+ const fieldsetSchema = buildYupSchema(field, config)();
693
+ newSchema[field.name] = fieldsetSchema;
694
+ } else {
695
+ newSchema[field.name] = field.schema;
696
+ }
697
+ } else {
698
+ Object.assign(newSchema, getSchema(field.fields, config));
699
+ }
700
+ }
701
+ });
702
+ return newSchema;
703
+ }
704
+ function buildCompleteYupSchema(fields, config) {
705
+ return object().shape(getSchema(fields, config), getNoSortEdges(fields));
706
+ }
707
+
708
+ // src/jsonLogic.js
709
+ function createValidationChecker(schema) {
710
+ const scopes = /* @__PURE__ */ new Map();
711
+ function createScopes(jsonSchema, key = "root") {
712
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
713
+ scopes.set(key, createValidationsScope(jsonSchema));
714
+ Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
715
+ if (property.type === "array") {
716
+ createScopes(property.items, `${key2}[]`);
717
+ } else {
718
+ createScopes(property, key2);
719
+ }
720
+ });
721
+ validateInlineRules(jsonSchema, sampleEmptyObject);
722
+ }
723
+ createScopes(schema);
724
+ return {
725
+ scopes,
726
+ getScope(name = "root") {
727
+ return scopes.get(name);
728
+ }
729
+ };
730
+ }
731
+ function createValidationsScope(schema) {
732
+ const validationMap = /* @__PURE__ */ new Map();
733
+ const computedValuesMap = /* @__PURE__ */ new Map();
734
+ const logic = schema?.["x-jsf-logic"] ?? {
735
+ validations: {},
736
+ computedValues: {}
737
+ };
738
+ const validations = Object.entries(logic.validations ?? {});
739
+ const computedValues = Object.entries(logic.computedValues ?? {});
740
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
741
+ validations.forEach(([id, validation]) => {
742
+ if (!validation.rule) {
743
+ throw Error(`[json-schema-form] json-logic error: Validation "${id}" has missing rule.`);
744
+ }
745
+ checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
746
+ validationMap.set(id, validation);
747
+ });
748
+ computedValues.forEach(([id, computedValue]) => {
749
+ if (!computedValue.rule) {
750
+ throw Error(`[json-schema-form] json-logic error: Computed value "${id}" has missing rule.`);
751
+ }
752
+ checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
753
+ computedValuesMap.set(id, computedValue);
754
+ });
755
+ function validate(rule, values) {
756
+ return jsonLogic.apply(
757
+ rule,
758
+ replaceUndefinedValuesWithNulls({ ...sampleEmptyObject, ...values })
759
+ );
760
+ }
761
+ return {
762
+ validationMap,
763
+ computedValuesMap,
764
+ validate,
765
+ applyValidationRuleInCondition(id, values) {
766
+ const validation = validationMap.get(id);
767
+ return validate(validation.rule, values);
768
+ },
769
+ applyComputedValueInField(id, values, fieldName) {
770
+ const validation = computedValuesMap.get(id);
771
+ if (validation === void 0) {
772
+ throw Error(
773
+ `[json-schema-form] json-logic error: Computed value "${id}" doesn't exist in field "${fieldName}".`
774
+ );
775
+ }
776
+ return validate(validation.rule, values);
777
+ },
778
+ applyComputedValueRuleInCondition(id, values) {
779
+ const validation = computedValuesMap.get(id);
780
+ return validate(validation.rule, values);
781
+ }
782
+ };
783
+ }
784
+ function replaceUndefinedValuesWithNulls(values = {}) {
785
+ return Object.entries(values).reduce((prev, [key, value]) => {
786
+ return { ...prev, [key]: value === void 0 || value === null ? NaN : value };
787
+ }, {});
788
+ }
789
+ function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
790
+ const { parentID = "root" } = config;
791
+ const validation = logic.getScope(parentID).validationMap.get(id);
792
+ if (validation === void 0) {
793
+ throw Error(
794
+ `[json-schema-form] json-logic error: "${field.name}" required validation "${id}" doesn't exist.`
795
+ );
796
+ }
797
+ return (yupSchema) => yupSchema.test(
798
+ `${field.name}-validation-${id}`,
799
+ validation?.errorMessage ?? "This field is invalid.",
800
+ (value, { parent }) => {
801
+ if (value === void 0 && !field.required)
802
+ return true;
803
+ return jsonLogic.apply(validation.rule, parent);
804
+ }
805
+ );
806
+ }
807
+ var HANDLEBARS_REGEX = /\{\{([^{}]+)\}\}/g;
808
+ function replaceHandlebarsTemplates({
809
+ value: toReplace,
810
+ logic,
811
+ formValues,
812
+ parentID,
813
+ name: fieldName
814
+ }) {
815
+ if (typeof toReplace === "string") {
816
+ return toReplace.replace(HANDLEBARS_REGEX, (match, key) => {
817
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
818
+ });
819
+ } else if (typeof toReplace === "object") {
820
+ const { value, ...rules } = toReplace;
821
+ if (Object.keys(rules).length > 1 && !value) {
822
+ throw Error("Cannot define multiple rules without a template string with key `value`.");
823
+ }
824
+ const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
825
+ const computedValue = logic.getScope(parentID).validate(rule, formValues);
826
+ return prev.replaceAll(`{{${key}}}`, computedValue);
827
+ }, value);
828
+ return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
829
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
830
+ });
831
+ }
832
+ return toReplace;
833
+ }
834
+ function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
835
+ return ({ logic, isRequired, config, formValues }) => {
836
+ const { name, computedAttributes } = fieldParams;
837
+ const attributes = Object.fromEntries(
838
+ Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
828
839
  );
840
+ return {
841
+ ...attributes,
842
+ schema: buildYupSchema(
843
+ { ...fieldParams, ...attributes, required: isRequired },
844
+ config,
845
+ logic
846
+ )
847
+ };
848
+ };
849
+ }
850
+ function handleComputedAttribute(logic, formValues, parentID, name) {
851
+ return ([key, value]) => {
852
+ switch (key) {
853
+ case "description":
854
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
855
+ case "title":
856
+ return ["label", replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
857
+ case "x-jsf-errorMessage":
858
+ return [
859
+ "errorMessage",
860
+ handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
861
+ ];
862
+ case "x-jsf-presentation": {
863
+ if (value.statement) {
864
+ return [
865
+ "statement",
866
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
867
+ ];
868
+ }
869
+ return [
870
+ key,
871
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
872
+ ];
873
+ }
874
+ case "const":
875
+ default: {
876
+ if (typeof value === "object" && value.rule) {
877
+ return [key, logic.getScope(parentID).validate(value.rule, formValues)];
878
+ }
879
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues, name)];
880
+ }
881
+ }
882
+ };
883
+ }
884
+ function handleNestedObjectForComputedValues(values, formValues, parentID, logic, name) {
885
+ return Object.fromEntries(
886
+ Object.entries(values).map(([key, value]) => {
887
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
888
+ })
889
+ );
890
+ }
891
+ function buildSampleEmptyObject(schema = {}) {
892
+ const sample = {};
893
+ if (typeof schema !== "object" || !schema.properties) {
894
+ return schema;
829
895
  }
830
- function withBaseSchema() {
831
- const customErrorMsg = errorMessage.type || errorMessageFromConfig.type;
832
- if (customErrorMsg) {
833
- return baseSchema.typeError(customErrorMsg);
896
+ for (const key in schema.properties) {
897
+ if (schema.properties[key].type === "object") {
898
+ sample[key] = buildSampleEmptyObject(schema.properties[key]);
899
+ } else if (schema.properties[key].type === "array") {
900
+ const itemSchema = schema.properties[key].items;
901
+ sample[key] = buildSampleEmptyObject(itemSchema);
902
+ } else {
903
+ sample[key] = true;
834
904
  }
835
- return baseSchema;
836
905
  }
837
- function buildFieldSetSchema(innerFields) {
838
- const fieldSetShape = {};
839
- innerFields.forEach((fieldSetfield) => {
840
- if (fieldSetfield.fields) {
841
- fieldSetShape[fieldSetfield.name] = object().shape(
842
- buildFieldSetSchema(fieldSetfield.fields)
906
+ return sample;
907
+ }
908
+ function validateInlineRules(jsonSchema, sampleEmptyObject) {
909
+ const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
910
+ Object.entries(properties).filter(([, property]) => property["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property]) => {
911
+ Object.entries(property["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
912
+ Object.values(item).forEach((rule) => {
913
+ checkRuleIntegrity(
914
+ rule,
915
+ fieldName,
916
+ sampleEmptyObject,
917
+ (item2) => `[json-schema-form] json-logic error: fieldName "${item2.var}" doesn't exist in field "${fieldName}.x-jsf-logic-computedAttrs.${key}".`
843
918
  );
919
+ });
920
+ });
921
+ });
922
+ }
923
+ function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`) {
924
+ Object.entries(rule ?? {}).map(([operator, subRule]) => {
925
+ if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
926
+ return;
927
+ throwIfUnknownOperator(operator, subRule, id);
928
+ subRule.map((item) => {
929
+ const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
930
+ if (isVar) {
931
+ const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, data);
932
+ if (exists === null) {
933
+ throw Error(errorMessage(item));
934
+ }
844
935
  } else {
845
- fieldSetShape[fieldSetfield.name] = buildYupSchema(
846
- {
847
- ...fieldSetfield,
848
- inputType: fieldSetfield.type
849
- },
850
- config
851
- )();
936
+ checkRuleIntegrity(item, id, data);
852
937
  }
853
938
  });
854
- return fieldSetShape;
855
- }
856
- function buildGroupArraySchema() {
857
- return object().shape(
858
- propertyFields.nthFieldGroup.fields().reduce(
859
- (schema, groupArrayField) => ({
860
- ...schema,
861
- [groupArrayField.name]: buildYupSchema(groupArrayField, config)()
862
- }),
863
- {}
864
- )
865
- );
866
- }
867
- const validators = [withBaseSchema];
868
- if (inputType === supportedTypes.GROUP_ARRAY) {
869
- validators[0] = () => withBaseSchema().of(buildGroupArraySchema());
870
- } else if (inputType === supportedTypes.FIELDSET) {
871
- validators[0] = () => withBaseSchema().shape(buildFieldSetSchema(propertyFields.fields));
872
- }
873
- if (propertyFields.required) {
874
- validators.push(withRequired);
875
- }
876
- if (typeof propertyFields.minimum !== "undefined") {
877
- validators.push(withMin);
878
- }
879
- if (typeof propertyFields.minLength !== "undefined") {
880
- validators.push(withMinLength);
881
- }
882
- if (propertyFields.maximum !== void 0) {
883
- validators.push(withMax);
884
- }
885
- if (propertyFields.maxLength) {
886
- validators.push(withMaxLength);
887
- }
888
- if (propertyFields.pattern) {
889
- validators.push(withMatches);
890
- }
891
- if (propertyFields.maxFileSize) {
892
- validators.push(withMaxFileSize);
893
- }
894
- if (propertyFields.accept) {
895
- validators.push(withFileFormat);
939
+ });
940
+ }
941
+ function throwIfUnknownOperator(operator, subRule, id) {
942
+ try {
943
+ jsonLogic.apply({ [operator]: subRule });
944
+ } catch (e) {
945
+ if (e.message === `Unrecognized operation ${operator}`) {
946
+ throw Error(
947
+ `[json-schema-form] json-logic error: in "${id}" rule there is an unknown operator "${operator}".`
948
+ );
949
+ }
896
950
  }
897
- if (propertyFields.const) {
898
- validators.push(withConst);
951
+ }
952
+ var regexToGetIndices = /\.\d+\./g;
953
+ function removeIndicesFromPath(path) {
954
+ const intermediatePath = path.replace(regexToGetIndices, ".");
955
+ return intermediatePath.replace(/\.\d+$/, "");
956
+ }
957
+ function processJSONLogicNode({
958
+ node,
959
+ formFields,
960
+ formValues,
961
+ accRequired,
962
+ parentID,
963
+ logic
964
+ }) {
965
+ const requiredFields = new Set(accRequired);
966
+ if (node.allOf) {
967
+ node.allOf.map(
968
+ (allOfNode) => processJSONLogicNode({ node: allOfNode, formValues, formFields, logic, parentID })
969
+ ).forEach(({ required: allOfItemRequired }) => {
970
+ allOfItemRequired.forEach(requiredFields.add, requiredFields);
971
+ });
899
972
  }
900
- if (propertyFields.requiredValidations) {
901
- propertyFields.requiredValidations.forEach(
902
- (id) => validators.push(yupSchemaWithCustomJSONLogic({ field, id, logic, config }))
973
+ if (node.if) {
974
+ const matchesPropertyCondition = checkIfConditionMatchesProperties(
975
+ node,
976
+ formValues,
977
+ formFields,
978
+ logic
903
979
  );
904
- }
905
- return flow(validators);
906
- }
907
- function getNoSortEdges(fields = []) {
908
- return fields.reduce((list, field) => {
909
- if (field.noSortEdges) {
910
- list.push(field.name);
980
+ const matchesValidationsAndComputedValues = matchesPropertyCondition && checkIfMatchesValidationsAndComputedValues(node, formValues, logic, parentID);
981
+ const isConditionMatch = matchesPropertyCondition && matchesValidationsAndComputedValues;
982
+ let nextNode;
983
+ if (isConditionMatch && node.then) {
984
+ nextNode = node.then;
911
985
  }
912
- return list;
913
- }, []);
914
- }
915
- function getSchema(fields = [], config) {
916
- const newSchema = {};
917
- fields.forEach((field) => {
918
- if (field.schema) {
919
- if (field.name) {
920
- if (field.inputType === supportedTypes.FIELDSET) {
921
- const fieldsetSchema = buildYupSchema(field, config)();
922
- newSchema[field.name] = fieldsetSchema;
923
- } else {
924
- newSchema[field.name] = field.schema;
925
- }
926
- } else {
927
- Object.assign(newSchema, getSchema(field.fields, config));
928
- }
986
+ if (!isConditionMatch && node.else) {
987
+ nextNode = node.else;
929
988
  }
930
- });
931
- return newSchema;
932
- }
933
- function buildCompleteYupSchema(fields, config) {
934
- return object().shape(getSchema(fields, config), getNoSortEdges(fields));
989
+ if (nextNode) {
990
+ const { required: branchRequired } = processNode({
991
+ node: nextNode,
992
+ formValues,
993
+ formFields,
994
+ accRequired,
995
+ logic,
996
+ parentID
997
+ });
998
+ branchRequired.forEach((field) => requiredFields.add(field));
999
+ }
1000
+ }
1001
+ return { required: requiredFields };
935
1002
  }
936
1003
 
937
1004
  // src/helpers.js
@@ -1050,7 +1117,11 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1050
1117
  updateValues(computedFieldValues);
1051
1118
  }
1052
1119
  if (field.calculateConditionalProperties) {
1053
- const newFieldValues = field.calculateConditionalProperties(fieldIsRequired, node);
1120
+ const newFieldValues = field.calculateConditionalProperties({
1121
+ isRequired: fieldIsRequired,
1122
+ conditionBranch: node,
1123
+ formValues
1124
+ });
1054
1125
  updateValues(newFieldValues);
1055
1126
  }
1056
1127
  if (field.calculateCustomValidationProperties) {
@@ -1082,7 +1153,7 @@ function processNode({
1082
1153
  });
1083
1154
  });
1084
1155
  if (node.if) {
1085
- const matchesCondition = checkIfConditionMatches(node, formValues, formFields, logic);
1156
+ const matchesCondition = checkIfConditionMatchesProperties(node, formValues, formFields, logic);
1086
1157
  if (matchesCondition && node.then) {
1087
1158
  const { required: branchRequired } = processNode({
1088
1159
  node: node.then,
@@ -1145,6 +1216,17 @@ function processNode({
1145
1216
  }
1146
1217
  });
1147
1218
  }
1219
+ if (node["x-jsf-logic"]) {
1220
+ const { required: requiredFromLogic } = processJSONLogicNode({
1221
+ node: node["x-jsf-logic"],
1222
+ formValues,
1223
+ formFields,
1224
+ accRequired: requiredFields,
1225
+ parentID,
1226
+ logic
1227
+ });
1228
+ requiredFromLogic.forEach((field) => requiredFields.add(field));
1229
+ }
1148
1230
  return {
1149
1231
  required: requiredFields
1150
1232
  };
@@ -1206,7 +1288,7 @@ function extractParametersFromNode(schemaNode) {
1206
1288
  }
1207
1289
  const presentation = pickXKey(schemaNode, "presentation") ?? {};
1208
1290
  const errorMessage = pickXKey(schemaNode, "errorMessage") ?? {};
1209
- const requiredValidations = schemaNode["x-jsf-logic-validations"];
1291
+ const jsonLogicValidations = schemaNode["x-jsf-logic-validations"];
1210
1292
  const computedAttributes = schemaNode["x-jsf-logic-computedAttrs"];
1211
1293
  const decoratedComputedAttributes = getDecoratedComputedAttributes(computedAttributes);
1212
1294
  const node = omit(schemaNode, ["x-jsf-presentation", "presentation"]);
@@ -1250,7 +1332,7 @@ function extractParametersFromNode(schemaNode) {
1250
1332
  },
1251
1333
  // Handle [name].presentation
1252
1334
  ...presentation,
1253
- requiredValidations,
1335
+ jsonLogicValidations,
1254
1336
  computedAttributes: decoratedComputedAttributes,
1255
1337
  description: containsHTML(description) ? wrapWithSpan(description, {
1256
1338
  class: "jsf-description"
@@ -1350,8 +1432,8 @@ function rebuildFieldset(fields, property) {
1350
1432
  required: isFieldRequired(property, field)
1351
1433
  }));
1352
1434
  }
1353
- function calculateConditionalProperties(fieldParams, customProperties) {
1354
- return (isRequired, conditionBranch) => {
1435
+ function calculateConditionalProperties({ fieldParams, customProperties, logic, config }) {
1436
+ return ({ isRequired, conditionBranch, formValues }) => {
1355
1437
  const conditionalProperty = conditionBranch?.properties?.[fieldParams.name];
1356
1438
  if (conditionalProperty) {
1357
1439
  const presentation = pickXKey(conditionalProperty, "presentation") ?? {};
@@ -1365,17 +1447,31 @@ function calculateConditionalProperties(fieldParams, customProperties) {
1365
1447
  fieldSetFields = rebuildFieldset(fieldParams.fields, conditionalProperty);
1366
1448
  newFieldParams.fields = fieldSetFields;
1367
1449
  }
1450
+ const { computedAttributes, ...restNewFieldParams } = newFieldParams;
1451
+ const calculatedComputedAttributes = computedAttributes ? calculateComputedAttributes(newFieldParams, config)({ logic, formValues }) : {};
1452
+ const jsonLogicValidations = [
1453
+ ...fieldParams.jsonLogicValidations ?? [],
1454
+ ...restNewFieldParams.jsonLogicValidations ?? []
1455
+ ];
1368
1456
  const base = {
1369
1457
  isVisible: true,
1370
1458
  required: isRequired,
1371
1459
  ...presentation?.inputType && { type: presentation.inputType },
1372
- schema: buildYupSchema({
1373
- ...fieldParams,
1374
- ...newFieldParams,
1375
- // If there are inner fields (case of fieldset) they need to be updated based on the condition
1376
- fields: fieldSetFields,
1377
- required: isRequired
1378
- })
1460
+ ...calculatedComputedAttributes,
1461
+ ...calculatedComputedAttributes.value ? { value: calculatedComputedAttributes.value } : { value: void 0 },
1462
+ schema: buildYupSchema(
1463
+ {
1464
+ ...fieldParams,
1465
+ ...restNewFieldParams,
1466
+ ...calculatedComputedAttributes,
1467
+ jsonLogicValidations,
1468
+ // If there are inner fields (case of fieldset) they need to be updated based on the condition
1469
+ fields: fieldSetFields,
1470
+ required: isRequired
1471
+ },
1472
+ config,
1473
+ logic
1474
+ )
1379
1475
  };
1380
1476
  return omit2(merge2(base, presentation, newFieldParams), ["inputType"]);
1381
1477
  }
@@ -1469,14 +1565,19 @@ function sortByOrderOrPosition(a, b, order) {
1469
1565
  function removeInvalidAttributes(fields) {
1470
1566
  return omit3(fields, ["items", "maxFileSize", "isDynamic"]);
1471
1567
  }
1472
- function buildFieldParameters(name, fieldProperties, required = [], config = {}) {
1568
+ function buildFieldParameters(name, fieldProperties, required = [], config = {}, logic) {
1473
1569
  const { position } = pickXKey(fieldProperties, "presentation") ?? {};
1474
1570
  let fields;
1475
1571
  const inputType = getInputType(fieldProperties, config.strictInputType, name);
1476
1572
  if (inputType === supportedTypes.FIELDSET) {
1477
- fields = getFieldsFromJSONSchema(fieldProperties, {
1478
- customProperties: get3(config, `customProperties.${name}`, {})
1479
- });
1573
+ fields = getFieldsFromJSONSchema(
1574
+ fieldProperties,
1575
+ {
1576
+ customProperties: get3(config, `customProperties.${name}`, {}),
1577
+ parentID: name
1578
+ },
1579
+ logic
1580
+ );
1480
1581
  }
1481
1582
  const result = {
1482
1583
  name,
@@ -1533,7 +1634,7 @@ function buildField(fieldParams, config, scopedJsonSchema, logic) {
1533
1634
  const customProperties = getCustomPropertiesForField(fieldParams, config);
1534
1635
  const composeFn = getComposeFunctionForField(fieldParams, !!customProperties);
1535
1636
  const yupSchema = buildYupSchema(fieldParams, config, logic);
1536
- const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties(fieldParams, customProperties);
1637
+ const calculateConditionalFieldsClosure = fieldParams.isDynamic && calculateConditionalProperties({ fieldParams, customProperties, logic, config });
1537
1638
  const calculateCustomValidationPropertiesClosure = calculateCustomValidationProperties(
1538
1639
  fieldParams,
1539
1640
  customProperties