@bluprynt/forms-core 3.0.0 → 4.1.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/README.md +12 -1
- package/dist/form-definition.schema.json +388 -2
- package/dist/index.cjs +1677 -51
- package/dist/index.d.cts +652 -47
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +652 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1673 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -110,6 +110,18 @@ const resolveRelativeDate = (relative, now) => {
|
|
|
110
110
|
*
|
|
111
111
|
* **Date handling**: condition values that are relative date expressions
|
|
112
112
|
* (e.g. `"+7d"`) are resolved against `ctx.now` before comparison.
|
|
113
|
+
*
|
|
114
|
+
* **Multiselect handling**: when `ctx.fieldTypes` reports the referenced field
|
|
115
|
+
* as `multiselect`, its array value is compared as a set instead of a scalar:
|
|
116
|
+
* `set` means "at least one option chosen", `eq`/`ne` compare set membership
|
|
117
|
+
* ignoring order and duplicates, `in`/`notin` test whether *any* chosen option
|
|
118
|
+
* appears in the condition's list, and the ordering operators are always
|
|
119
|
+
* `false`. Fields of every other type -- including `array` -- keep the original
|
|
120
|
+
* scalar semantics.
|
|
121
|
+
*
|
|
122
|
+
* **Object-valued fields** (`file`, `sustainability`) go through the scalar path
|
|
123
|
+
* too, so only `set`/`notset` are meaningful: any object present counts as
|
|
124
|
+
* `set`, and `eq`/`ne`/`in`/`notin` compare by reference and so never match.
|
|
113
125
|
*/
|
|
114
126
|
var ConditionEvaluator = class {
|
|
115
127
|
/**
|
|
@@ -128,6 +140,7 @@ var ConditionEvaluator = class {
|
|
|
128
140
|
evalSimple(cond, ctx) {
|
|
129
141
|
if (ctx.visibilityMap && ctx.visibilityMap.get(cond.field) === false) return cond.op === "notset";
|
|
130
142
|
const fieldValue = ctx.values[String(cond.field)];
|
|
143
|
+
if (ctx.fieldTypes?.get(cond.field) === "multiselect") return this.evalMultiselect(cond, fieldValue);
|
|
131
144
|
switch (cond.op) {
|
|
132
145
|
case "set": return fieldValue !== null && fieldValue !== void 0 && fieldValue !== "";
|
|
133
146
|
case "notset": return fieldValue === null || fieldValue === void 0 || fieldValue === "";
|
|
@@ -142,6 +155,32 @@ var ConditionEvaluator = class {
|
|
|
142
155
|
default: return false;
|
|
143
156
|
}
|
|
144
157
|
}
|
|
158
|
+
evalMultiselect(cond, fieldValue) {
|
|
159
|
+
const selected = Array.isArray(fieldValue) ? fieldValue : [];
|
|
160
|
+
const candidates = cond.value;
|
|
161
|
+
switch (cond.op) {
|
|
162
|
+
case "set": return selected.length > 0;
|
|
163
|
+
case "notset": return selected.length === 0;
|
|
164
|
+
case "eq": return this.sameSet(selected, candidates);
|
|
165
|
+
case "ne": return !this.sameSet(selected, candidates);
|
|
166
|
+
case "in": return Array.isArray(candidates) && selected.some((item) => candidates.includes(item));
|
|
167
|
+
case "notin": return Array.isArray(candidates) && !selected.some((item) => candidates.includes(item));
|
|
168
|
+
default: return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Compares the chosen options against a condition value as sets, ignoring
|
|
173
|
+
* order and duplicates. A scalar condition value is read as a one-element
|
|
174
|
+
* set, so `{ op: 'eq', value: 'a' }` means "`a` is the only option chosen".
|
|
175
|
+
*/
|
|
176
|
+
sameSet(selected, expected) {
|
|
177
|
+
const expectedItems = Array.isArray(expected) ? expected : [expected];
|
|
178
|
+
const selectedSet = new Set(selected);
|
|
179
|
+
const expectedSet = new Set(expectedItems);
|
|
180
|
+
if (selectedSet.size !== expectedSet.size) return false;
|
|
181
|
+
for (const item of expectedSet) if (!selectedSet.has(item)) return false;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
145
184
|
resolveIfDate(value, now) {
|
|
146
185
|
if (isRelativeDate(value)) return resolveRelativeDate(value, now);
|
|
147
186
|
return value;
|
|
@@ -402,6 +441,93 @@ var DependencyGraph = class DependencyGraph {
|
|
|
402
441
|
}
|
|
403
442
|
};
|
|
404
443
|
//#endregion
|
|
444
|
+
//#region src/validators/array-obj-validator.ts
|
|
445
|
+
const isRow = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
446
|
+
/**
|
|
447
|
+
* Validates an `array_obj` field: the row list itself, then every sub-field of
|
|
448
|
+
* every row.
|
|
449
|
+
*
|
|
450
|
+
* Sub-field errors keep the container's `fieldId` -- matching the key the
|
|
451
|
+
* `fieldErrors` map stores them under -- and carry `itemIndex` (the row) plus
|
|
452
|
+
* `itemFieldId` (the sub-field). A row-level `TYPE` error carries `itemIndex`
|
|
453
|
+
* only.
|
|
454
|
+
*/
|
|
455
|
+
var ArrayObjValidator = class {
|
|
456
|
+
validate(ctx) {
|
|
457
|
+
const { fieldId, value, now } = ctx;
|
|
458
|
+
const { items, validateField } = ctx;
|
|
459
|
+
const validation = ctx.validation;
|
|
460
|
+
const errors = [];
|
|
461
|
+
if (value === null || value === void 0) return errors;
|
|
462
|
+
if (!Array.isArray(value)) {
|
|
463
|
+
errors.push({
|
|
464
|
+
fieldId,
|
|
465
|
+
rule: "TYPE",
|
|
466
|
+
message: "Must be an array",
|
|
467
|
+
params: { expectedType: "array" }
|
|
468
|
+
});
|
|
469
|
+
return errors;
|
|
470
|
+
}
|
|
471
|
+
if (validation?.minItems !== void 0 && value.length < validation.minItems) errors.push({
|
|
472
|
+
fieldId,
|
|
473
|
+
rule: "MIN_ITEMS",
|
|
474
|
+
message: `Must have at least ${validation.minItems} items`,
|
|
475
|
+
params: {
|
|
476
|
+
minItems: validation.minItems,
|
|
477
|
+
actual: value.length
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
if (validation?.maxItems !== void 0 && value.length > validation.maxItems) errors.push({
|
|
481
|
+
fieldId,
|
|
482
|
+
rule: "MAX_ITEMS",
|
|
483
|
+
message: `Must have at most ${validation.maxItems} items`,
|
|
484
|
+
params: {
|
|
485
|
+
maxItems: validation.maxItems,
|
|
486
|
+
actual: value.length
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
if (!items) return errors;
|
|
490
|
+
for (let i = 0; i < value.length; i++) {
|
|
491
|
+
const raw = value[i];
|
|
492
|
+
if (raw !== null && raw !== void 0 && !isRow(raw)) {
|
|
493
|
+
errors.push({
|
|
494
|
+
fieldId,
|
|
495
|
+
rule: "TYPE",
|
|
496
|
+
message: "Must be an object",
|
|
497
|
+
params: { expectedType: "object" },
|
|
498
|
+
itemIndex: i
|
|
499
|
+
});
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const row = isRow(raw) ? raw : {};
|
|
503
|
+
for (const subField of items) {
|
|
504
|
+
if (subField.type === "static") continue;
|
|
505
|
+
const fakeEntry = {
|
|
506
|
+
id: subField.id,
|
|
507
|
+
type: subField.type,
|
|
508
|
+
condition: void 0,
|
|
509
|
+
validation: subField.validation,
|
|
510
|
+
parentId: fieldId,
|
|
511
|
+
options: subField.options,
|
|
512
|
+
item: void 0,
|
|
513
|
+
items: void 0,
|
|
514
|
+
label: subField.label,
|
|
515
|
+
title: void 0
|
|
516
|
+
};
|
|
517
|
+
const subErrors = validateField(subField.id, row[String(subField.id)], fakeEntry, now);
|
|
518
|
+
errors.push(...subErrors.map((err) => ({
|
|
519
|
+
...err,
|
|
520
|
+
fieldId,
|
|
521
|
+
itemIndex: i,
|
|
522
|
+
itemFieldId: subField.id,
|
|
523
|
+
message: `${subField.label}: ${err.message}`
|
|
524
|
+
})));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return errors;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
//#endregion
|
|
405
531
|
//#region src/validators/array-validator.ts
|
|
406
532
|
var ArrayValidator = class {
|
|
407
533
|
validate(ctx) {
|
|
@@ -437,7 +563,7 @@ var ArrayValidator = class {
|
|
|
437
563
|
actual: value.length
|
|
438
564
|
}
|
|
439
565
|
});
|
|
440
|
-
if (item) for (let i = 0; i < value.length; i++) {
|
|
566
|
+
if (item && item.type !== "static") for (let i = 0; i < value.length; i++) {
|
|
441
567
|
const fakeEntry = {
|
|
442
568
|
id: fieldId,
|
|
443
569
|
type: item.type,
|
|
@@ -446,6 +572,7 @@ var ArrayValidator = class {
|
|
|
446
572
|
parentId: void 0,
|
|
447
573
|
options: item.options,
|
|
448
574
|
item: void 0,
|
|
575
|
+
items: void 0,
|
|
449
576
|
label: item.label,
|
|
450
577
|
title: void 0
|
|
451
578
|
};
|
|
@@ -459,6 +586,32 @@ var ArrayValidator = class {
|
|
|
459
586
|
}
|
|
460
587
|
};
|
|
461
588
|
//#endregion
|
|
589
|
+
//#region src/validators/blockchain-validator.ts
|
|
590
|
+
var BlockchainValidator = class {
|
|
591
|
+
validate(ctx) {
|
|
592
|
+
const { fieldId, value } = ctx;
|
|
593
|
+
const validation = ctx.validation;
|
|
594
|
+
const errors = [];
|
|
595
|
+
const isEmpty = value === null || value === void 0 || value === "";
|
|
596
|
+
if (validation?.required && isEmpty) {
|
|
597
|
+
errors.push({
|
|
598
|
+
fieldId,
|
|
599
|
+
rule: "REQUIRED",
|
|
600
|
+
message: "Value is required"
|
|
601
|
+
});
|
|
602
|
+
return errors;
|
|
603
|
+
}
|
|
604
|
+
if (isEmpty) return errors;
|
|
605
|
+
if (typeof value !== "string") errors.push({
|
|
606
|
+
fieldId,
|
|
607
|
+
rule: "TYPE",
|
|
608
|
+
message: "Must be a blockchain address string",
|
|
609
|
+
params: { expectedType: "blockchain" }
|
|
610
|
+
});
|
|
611
|
+
return errors;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
//#endregion
|
|
462
615
|
//#region src/validators/boolean-validator.ts
|
|
463
616
|
var BooleanValidator = class {
|
|
464
617
|
validate(ctx) {
|
|
@@ -556,6 +709,51 @@ var FileValidator = class {
|
|
|
556
709
|
}
|
|
557
710
|
};
|
|
558
711
|
//#endregion
|
|
712
|
+
//#region src/validators/multiselect-validator.ts
|
|
713
|
+
/**
|
|
714
|
+
* Validates `multiselect` fields, whose value is an array of the chosen
|
|
715
|
+
* options' `value`s.
|
|
716
|
+
*
|
|
717
|
+
* An absent value and an empty array both count as "nothing selected", so both
|
|
718
|
+
* fail `required`. Option membership is reported as a single field-level
|
|
719
|
+
* `INVALID_OPTION` error rather than one error per offending element: the
|
|
720
|
+
* viewer filters out errors carrying an `itemIndex` on the non-array render
|
|
721
|
+
* path, so indexed errors would never be displayed.
|
|
722
|
+
*/
|
|
723
|
+
var MultiselectValidator = class {
|
|
724
|
+
validate(ctx) {
|
|
725
|
+
const { fieldId, value } = ctx;
|
|
726
|
+
const validation = ctx.validation;
|
|
727
|
+
const options = ctx.options;
|
|
728
|
+
const errors = [];
|
|
729
|
+
const isEmpty = value === null || value === void 0 || Array.isArray(value) && value.length === 0;
|
|
730
|
+
if (validation?.required && isEmpty) {
|
|
731
|
+
errors.push({
|
|
732
|
+
fieldId,
|
|
733
|
+
rule: "REQUIRED",
|
|
734
|
+
message: "Value is required"
|
|
735
|
+
});
|
|
736
|
+
return errors;
|
|
737
|
+
}
|
|
738
|
+
if (isEmpty) return errors;
|
|
739
|
+
if (!Array.isArray(value)) {
|
|
740
|
+
errors.push({
|
|
741
|
+
fieldId,
|
|
742
|
+
rule: "TYPE",
|
|
743
|
+
message: "Must be an array",
|
|
744
|
+
params: { expectedType: "multiselect" }
|
|
745
|
+
});
|
|
746
|
+
return errors;
|
|
747
|
+
}
|
|
748
|
+
if (options && value.some((selected) => !options.some((opt) => opt.value === selected))) errors.push({
|
|
749
|
+
fieldId,
|
|
750
|
+
rule: "INVALID_OPTION",
|
|
751
|
+
message: "Value is not a valid option"
|
|
752
|
+
});
|
|
753
|
+
return errors;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
//#endregion
|
|
559
757
|
//#region src/validators/number-validator.ts
|
|
560
758
|
var NumberValidator = class {
|
|
561
759
|
validate(ctx) {
|
|
@@ -683,6 +881,53 @@ var StringValidator = class {
|
|
|
683
881
|
}
|
|
684
882
|
};
|
|
685
883
|
//#endregion
|
|
884
|
+
//#region src/validators/sustainability-validator.ts
|
|
885
|
+
const OPTIONAL_TEXT_PROPERTIES = ["ccri", "cmc"];
|
|
886
|
+
var SustainabilityValidator = class {
|
|
887
|
+
validate(ctx) {
|
|
888
|
+
const { fieldId, value } = ctx;
|
|
889
|
+
const validation = ctx.validation;
|
|
890
|
+
const errors = [];
|
|
891
|
+
if (value === null || value === void 0) {
|
|
892
|
+
if (validation?.required) errors.push({
|
|
893
|
+
fieldId,
|
|
894
|
+
rule: "REQUIRED",
|
|
895
|
+
message: "Value is required"
|
|
896
|
+
});
|
|
897
|
+
return errors;
|
|
898
|
+
}
|
|
899
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
900
|
+
errors.push({
|
|
901
|
+
fieldId,
|
|
902
|
+
rule: "TYPE",
|
|
903
|
+
message: "Must be a valid sustainability object",
|
|
904
|
+
params: { expectedType: "sustainability" }
|
|
905
|
+
});
|
|
906
|
+
return errors;
|
|
907
|
+
}
|
|
908
|
+
const record = value;
|
|
909
|
+
const address = record.address ?? "";
|
|
910
|
+
if (typeof address !== "string" || OPTIONAL_TEXT_PROPERTIES.some((key) => !isOptionalText(record[key]))) {
|
|
911
|
+
errors.push({
|
|
912
|
+
fieldId,
|
|
913
|
+
rule: "TYPE",
|
|
914
|
+
message: "Must be a valid sustainability object",
|
|
915
|
+
params: { expectedType: "sustainability" }
|
|
916
|
+
});
|
|
917
|
+
return errors;
|
|
918
|
+
}
|
|
919
|
+
if (validation?.required && address === "") errors.push({
|
|
920
|
+
fieldId,
|
|
921
|
+
rule: "REQUIRED",
|
|
922
|
+
message: "Value is required"
|
|
923
|
+
});
|
|
924
|
+
return errors;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
function isOptionalText(value) {
|
|
928
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
929
|
+
}
|
|
930
|
+
//#endregion
|
|
686
931
|
//#region src/field-validator.ts
|
|
687
932
|
/**
|
|
688
933
|
* Validates form values against the schema's validation rules.
|
|
@@ -699,9 +944,19 @@ var StringValidator = class {
|
|
|
699
944
|
* - `date` -- `required`, `minDate`, `maxDate`. Relative date boundaries
|
|
700
945
|
* are resolved against `now`.
|
|
701
946
|
* - `select` -- `required`, plus the value must be one of the defined options.
|
|
947
|
+
* - `multiselect` -- `required` (an absent value and an empty array both fail),
|
|
948
|
+
* plus every selected value must be one of the defined options.
|
|
949
|
+
* - `static` -- never validated; static blocks hold no value.
|
|
950
|
+
* - `blockchain` -- `required` only. The CAIP address format is never checked.
|
|
951
|
+
* - `sustainability` -- `required` (the value's `address` must be a non-empty
|
|
952
|
+
* string) plus a shape check; `ccri` and `cmc` are optional text.
|
|
702
953
|
* - `array` -- `minItems`, `maxItems`, plus each item is validated
|
|
703
954
|
* individually according to the array's {@link ArrayItemDef}. Item-level
|
|
704
955
|
* errors carry an `itemIndex`.
|
|
956
|
+
* - `array_obj` -- `minItems`, `maxItems`, plus every sub-field of every row is
|
|
957
|
+
* validated according to the field's {@link ArrayObjItemDef} list. Sub-field
|
|
958
|
+
* errors carry an `itemIndex` (the row) and an `itemFieldId` (the sub-field),
|
|
959
|
+
* while `fieldId` stays the container's id.
|
|
705
960
|
*
|
|
706
961
|
* For all types, if `required` fails, no further rules are checked for that
|
|
707
962
|
* field (early return). If the value is empty/absent and `required` is not
|
|
@@ -721,8 +976,12 @@ var FieldValidator = class {
|
|
|
721
976
|
boolean: new BooleanValidator(),
|
|
722
977
|
date: new DateValidator(),
|
|
723
978
|
select: new SelectValidator(),
|
|
979
|
+
multiselect: new MultiselectValidator(),
|
|
724
980
|
array: new ArrayValidator(),
|
|
725
|
-
|
|
981
|
+
array_obj: new ArrayObjValidator(),
|
|
982
|
+
file: new FileValidator(),
|
|
983
|
+
blockchain: new BlockchainValidator(),
|
|
984
|
+
sustainability: new SustainabilityValidator()
|
|
726
985
|
};
|
|
727
986
|
}
|
|
728
987
|
/**
|
|
@@ -763,6 +1022,17 @@ var FieldValidator = class {
|
|
|
763
1022
|
};
|
|
764
1023
|
return validator.validate(ctx);
|
|
765
1024
|
}
|
|
1025
|
+
if (entry.type === "array_obj") {
|
|
1026
|
+
const ctx = {
|
|
1027
|
+
fieldId,
|
|
1028
|
+
value,
|
|
1029
|
+
validation: entry.validation,
|
|
1030
|
+
now,
|
|
1031
|
+
items: entry.items,
|
|
1032
|
+
validateField: this.validateField.bind(this)
|
|
1033
|
+
};
|
|
1034
|
+
return validator.validate(ctx);
|
|
1035
|
+
}
|
|
766
1036
|
return validator.validate({
|
|
767
1037
|
fieldId,
|
|
768
1038
|
value,
|
|
@@ -817,11 +1087,17 @@ var FormDefinitionEditor = class {
|
|
|
817
1087
|
}
|
|
818
1088
|
/**
|
|
819
1089
|
* Returns the next available numeric id (max existing + 1).
|
|
1090
|
+
*
|
|
1091
|
+
* `array_obj` sub-field ids share the form-wide id space, so they count
|
|
1092
|
+
* here too -- otherwise a new top-level field could collide with one.
|
|
820
1093
|
*/
|
|
821
1094
|
nextId() {
|
|
822
1095
|
let max = 0;
|
|
823
1096
|
this.walkAll(this.definition.content, (item) => {
|
|
824
1097
|
if (item.id > max) max = item.id;
|
|
1098
|
+
if (item.type === "array_obj") {
|
|
1099
|
+
for (const subField of item.items ?? []) if (subField.id > max) max = subField.id;
|
|
1100
|
+
}
|
|
825
1101
|
});
|
|
826
1102
|
return max + 1;
|
|
827
1103
|
}
|
|
@@ -960,6 +1236,7 @@ var FormDefinitionEditor = class {
|
|
|
960
1236
|
const item = this.findItem(id);
|
|
961
1237
|
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
962
1238
|
if (item.type === "section") throw new Error("Sections do not have validation");
|
|
1239
|
+
if (item.type === "static") throw new Error("Static fields do not have validation");
|
|
963
1240
|
const field = item;
|
|
964
1241
|
if (validation === void 0) delete field.validation;
|
|
965
1242
|
else field.validation = validation;
|
|
@@ -976,12 +1253,12 @@ var FormDefinitionEditor = class {
|
|
|
976
1253
|
return this;
|
|
977
1254
|
}
|
|
978
1255
|
/**
|
|
979
|
-
* Sets the
|
|
1256
|
+
* Sets the options for a `select` or `multiselect` field.
|
|
980
1257
|
*/
|
|
981
1258
|
setOptions(id, options) {
|
|
982
1259
|
const item = this.findItem(id);
|
|
983
1260
|
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
984
|
-
if (item.type !== "select") throw new Error(`Field ${id} is not a select field`);
|
|
1261
|
+
if (item.type !== "select" && item.type !== "multiselect") throw new Error(`Field ${id} is not a select or multiselect field`);
|
|
985
1262
|
const field = item;
|
|
986
1263
|
field.options = options;
|
|
987
1264
|
return this;
|
|
@@ -998,6 +1275,94 @@ var FormDefinitionEditor = class {
|
|
|
998
1275
|
return this;
|
|
999
1276
|
}
|
|
1000
1277
|
/**
|
|
1278
|
+
* Replaces the whole sub-field list of an `array_obj` field.
|
|
1279
|
+
*
|
|
1280
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1281
|
+
*/
|
|
1282
|
+
setArrayItems(id, itemDefs) {
|
|
1283
|
+
this.assertArrayObjField(id).items = itemDefs;
|
|
1284
|
+
return this;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Returns the sub-field list of an `array_obj` field.
|
|
1288
|
+
*
|
|
1289
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1290
|
+
*/
|
|
1291
|
+
getArrayItems(id) {
|
|
1292
|
+
return this.assertArrayObjField(id).items ?? [];
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Sets or clears the layout hint of an `array_obj` field.
|
|
1296
|
+
*
|
|
1297
|
+
* Presentation metadata only -- the engine never reads it. Passing
|
|
1298
|
+
* `undefined` removes the key, which reads as `'list'`.
|
|
1299
|
+
*
|
|
1300
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1301
|
+
*/
|
|
1302
|
+
setArrayKind(id, kind) {
|
|
1303
|
+
const field = this.assertArrayObjField(id);
|
|
1304
|
+
if (kind === void 0) delete field.kind;
|
|
1305
|
+
else field.kind = kind;
|
|
1306
|
+
return this;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Appends a sub-field to an `array_obj` field. The sub-field's `id` is
|
|
1310
|
+
* auto-assigned when omitted.
|
|
1311
|
+
*
|
|
1312
|
+
* @returns The id of the sub-field that was added.
|
|
1313
|
+
* @throws If the id is not found, does not refer to an `array_obj` field,
|
|
1314
|
+
* or the requested sub-field id is already taken.
|
|
1315
|
+
*/
|
|
1316
|
+
addArrayItemField(id, subField) {
|
|
1317
|
+
const field = this.assertArrayObjField(id);
|
|
1318
|
+
const subFieldId = subField.id ?? this.nextId();
|
|
1319
|
+
this.assertIdAvailable(subFieldId);
|
|
1320
|
+
field.items?.push({
|
|
1321
|
+
...subField,
|
|
1322
|
+
id: subFieldId
|
|
1323
|
+
});
|
|
1324
|
+
return subFieldId;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Merges properties into an existing `array_obj` sub-field.
|
|
1328
|
+
*
|
|
1329
|
+
* Like {@link updateField}, omitted properties are kept rather than
|
|
1330
|
+
* removed -- pass a property explicitly as `undefined` to clear it.
|
|
1331
|
+
*
|
|
1332
|
+
* @throws If the field or the sub-field is not found.
|
|
1333
|
+
*/
|
|
1334
|
+
updateArrayItemField(id, subFieldId, patch) {
|
|
1335
|
+
const subField = this.assertArrayObjField(id).items?.find((f) => f.id === subFieldId);
|
|
1336
|
+
if (!subField) throw new Error(`Sub-field with id ${subFieldId} not found in field ${id}`);
|
|
1337
|
+
Object.assign(subField, patch, { id: subFieldId });
|
|
1338
|
+
return this;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Removes a sub-field from an `array_obj` field.
|
|
1342
|
+
*
|
|
1343
|
+
* @throws If the field or the sub-field is not found.
|
|
1344
|
+
*/
|
|
1345
|
+
removeArrayItemField(id, subFieldId) {
|
|
1346
|
+
const field = this.assertArrayObjField(id);
|
|
1347
|
+
const index = field.items?.findIndex((f) => f.id === subFieldId) ?? -1;
|
|
1348
|
+
if (index < 0) throw new Error(`Sub-field with id ${subFieldId} not found in field ${id}`);
|
|
1349
|
+
field.items?.splice(index, 1);
|
|
1350
|
+
return this;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Moves a sub-field of an `array_obj` field to another position.
|
|
1354
|
+
*
|
|
1355
|
+
* @throws If the field is not found or either index is out of range.
|
|
1356
|
+
*/
|
|
1357
|
+
moveArrayItemField(id, fromIndex, toIndex) {
|
|
1358
|
+
const items = this.assertArrayObjField(id).items ?? [];
|
|
1359
|
+
if (fromIndex < 0 || fromIndex >= items.length) throw new Error(`Index ${fromIndex} out of range`);
|
|
1360
|
+
if (toIndex < 0 || toIndex >= items.length) throw new Error(`Index ${toIndex} out of range`);
|
|
1361
|
+
const [moved] = items.splice(fromIndex, 1);
|
|
1362
|
+
if (moved) items.splice(toIndex, 0, moved);
|
|
1363
|
+
return this;
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1001
1366
|
* Sets the label for a field.
|
|
1002
1367
|
*/
|
|
1003
1368
|
setLabel(id, label) {
|
|
@@ -1009,6 +1374,18 @@ var FormDefinitionEditor = class {
|
|
|
1009
1374
|
return this;
|
|
1010
1375
|
}
|
|
1011
1376
|
/**
|
|
1377
|
+
* Sets the displayed text of a `static` field.
|
|
1378
|
+
*
|
|
1379
|
+
* @throws If the id is not found or does not refer to a static field.
|
|
1380
|
+
*/
|
|
1381
|
+
setText(id, text) {
|
|
1382
|
+
const item = this.findItem(id);
|
|
1383
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1384
|
+
if (item.type !== "static") throw new Error(`Field ${id} is not a static field`);
|
|
1385
|
+
item.text = text;
|
|
1386
|
+
return this;
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1012
1389
|
* Sets the description for a field or section.
|
|
1013
1390
|
*/
|
|
1014
1391
|
setFieldDescription(id, description) {
|
|
@@ -1019,6 +1396,41 @@ var FormDefinitionEditor = class {
|
|
|
1019
1396
|
return this;
|
|
1020
1397
|
}
|
|
1021
1398
|
/**
|
|
1399
|
+
* Sets the placeholder for a field or section.
|
|
1400
|
+
*
|
|
1401
|
+
* Passing `undefined` removes the property. Unlike {@link updateField},
|
|
1402
|
+
* which merges and therefore cannot clear, this genuinely deletes the key.
|
|
1403
|
+
*/
|
|
1404
|
+
setPlaceholder(id, placeholder) {
|
|
1405
|
+
return this.setMetadata(id, "placeholder", placeholder);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Sets the answer guidelines for a field or section.
|
|
1409
|
+
*
|
|
1410
|
+
* Passing `undefined` removes the property.
|
|
1411
|
+
*/
|
|
1412
|
+
setAnswerGuidelines(id, answerGuidelines) {
|
|
1413
|
+
return this.setMetadata(id, "answer_guidelines", answerGuidelines);
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Sets the external reference id for a field or section.
|
|
1417
|
+
*
|
|
1418
|
+
* Passing `undefined` removes the property.
|
|
1419
|
+
*/
|
|
1420
|
+
setReferenceId(id, referenceId) {
|
|
1421
|
+
return this.setMetadata(id, "reference_id", referenceId);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Assigns or deletes one optional string property on a field or section.
|
|
1425
|
+
*/
|
|
1426
|
+
setMetadata(id, property, value) {
|
|
1427
|
+
const item = this.findItem(id);
|
|
1428
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1429
|
+
if (value === void 0) delete item[property];
|
|
1430
|
+
else item[property] = value;
|
|
1431
|
+
return this;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1022
1434
|
* Returns a deep clone of the current form definition.
|
|
1023
1435
|
*/
|
|
1024
1436
|
toJSON() {
|
|
@@ -1033,6 +1445,30 @@ var FormDefinitionEditor = class {
|
|
|
1033
1445
|
}
|
|
1034
1446
|
assertIdAvailable(id) {
|
|
1035
1447
|
if (this.findItem(id)) throw new Error(`Item with id ${id} already exists`);
|
|
1448
|
+
if (this.findArrayObjItem(id)) throw new Error(`Item with id ${id} already exists`);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Finds an `array_obj` sub-field anywhere in the definition by its id.
|
|
1452
|
+
*/
|
|
1453
|
+
findArrayObjItem(id) {
|
|
1454
|
+
let found;
|
|
1455
|
+
this.walkAll(this.definition.content, (item) => {
|
|
1456
|
+
if (found || item.type !== "array_obj") return;
|
|
1457
|
+
const subField = (item.items ?? []).find((f) => f.id === id);
|
|
1458
|
+
if (subField) found = {
|
|
1459
|
+
field: item,
|
|
1460
|
+
subField
|
|
1461
|
+
};
|
|
1462
|
+
});
|
|
1463
|
+
return found;
|
|
1464
|
+
}
|
|
1465
|
+
assertArrayObjField(id) {
|
|
1466
|
+
const item = this.findItem(id);
|
|
1467
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1468
|
+
if (item.type !== "array_obj") throw new Error(`Field ${id} is not an array_obj field`);
|
|
1469
|
+
const field = item;
|
|
1470
|
+
if (!field.items) field.items = [];
|
|
1471
|
+
return field;
|
|
1036
1472
|
}
|
|
1037
1473
|
insertItem(item, parentId, index) {
|
|
1038
1474
|
const target = this.getTargetContent(parentId);
|
|
@@ -1244,6 +1680,11 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1244
1680
|
"properties": { "required": { "type": "boolean" } },
|
|
1245
1681
|
"additionalProperties": false
|
|
1246
1682
|
},
|
|
1683
|
+
"multiselectValidation": {
|
|
1684
|
+
"type": "object",
|
|
1685
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1686
|
+
"additionalProperties": false
|
|
1687
|
+
},
|
|
1247
1688
|
"arrayValidation": {
|
|
1248
1689
|
"type": "object",
|
|
1249
1690
|
"properties": {
|
|
@@ -1258,11 +1699,35 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1258
1699
|
},
|
|
1259
1700
|
"additionalProperties": false
|
|
1260
1701
|
},
|
|
1702
|
+
"arrayObjValidation": {
|
|
1703
|
+
"type": "object",
|
|
1704
|
+
"properties": {
|
|
1705
|
+
"minItems": {
|
|
1706
|
+
"type": "integer",
|
|
1707
|
+
"minimum": 0
|
|
1708
|
+
},
|
|
1709
|
+
"maxItems": {
|
|
1710
|
+
"type": "integer",
|
|
1711
|
+
"minimum": 1
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
"additionalProperties": false
|
|
1715
|
+
},
|
|
1261
1716
|
"fileValidation": {
|
|
1262
1717
|
"type": "object",
|
|
1263
1718
|
"properties": { "required": { "type": "boolean" } },
|
|
1264
1719
|
"additionalProperties": false
|
|
1265
1720
|
},
|
|
1721
|
+
"blockchainValidation": {
|
|
1722
|
+
"type": "object",
|
|
1723
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1724
|
+
"additionalProperties": false
|
|
1725
|
+
},
|
|
1726
|
+
"sustainabilityValidation": {
|
|
1727
|
+
"type": "object",
|
|
1728
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1729
|
+
"additionalProperties": false
|
|
1730
|
+
},
|
|
1266
1731
|
"selectOption": {
|
|
1267
1732
|
"type": "object",
|
|
1268
1733
|
"required": ["value", "label"],
|
|
@@ -1279,6 +1744,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1279
1744
|
"type": { "const": "string" },
|
|
1280
1745
|
"label": { "type": "string" },
|
|
1281
1746
|
"description": { "type": "string" },
|
|
1747
|
+
"placeholder": { "type": "string" },
|
|
1748
|
+
"answer_guidelines": { "type": "string" },
|
|
1749
|
+
"reference_id": { "type": "string" },
|
|
1282
1750
|
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1283
1751
|
},
|
|
1284
1752
|
"additionalProperties": false
|
|
@@ -1290,6 +1758,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1290
1758
|
"type": { "const": "number" },
|
|
1291
1759
|
"label": { "type": "string" },
|
|
1292
1760
|
"description": { "type": "string" },
|
|
1761
|
+
"placeholder": { "type": "string" },
|
|
1762
|
+
"answer_guidelines": { "type": "string" },
|
|
1763
|
+
"reference_id": { "type": "string" },
|
|
1293
1764
|
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1294
1765
|
},
|
|
1295
1766
|
"additionalProperties": false
|
|
@@ -1301,6 +1772,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1301
1772
|
"type": { "const": "boolean" },
|
|
1302
1773
|
"label": { "type": "string" },
|
|
1303
1774
|
"description": { "type": "string" },
|
|
1775
|
+
"placeholder": { "type": "string" },
|
|
1776
|
+
"answer_guidelines": { "type": "string" },
|
|
1777
|
+
"reference_id": { "type": "string" },
|
|
1304
1778
|
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1305
1779
|
},
|
|
1306
1780
|
"additionalProperties": false
|
|
@@ -1312,6 +1786,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1312
1786
|
"type": { "const": "date" },
|
|
1313
1787
|
"label": { "type": "string" },
|
|
1314
1788
|
"description": { "type": "string" },
|
|
1789
|
+
"placeholder": { "type": "string" },
|
|
1790
|
+
"answer_guidelines": { "type": "string" },
|
|
1791
|
+
"reference_id": { "type": "string" },
|
|
1315
1792
|
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1316
1793
|
},
|
|
1317
1794
|
"additionalProperties": false
|
|
@@ -1327,6 +1804,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1327
1804
|
"type": { "const": "select" },
|
|
1328
1805
|
"label": { "type": "string" },
|
|
1329
1806
|
"description": { "type": "string" },
|
|
1807
|
+
"placeholder": { "type": "string" },
|
|
1808
|
+
"answer_guidelines": { "type": "string" },
|
|
1809
|
+
"reference_id": { "type": "string" },
|
|
1330
1810
|
"options": {
|
|
1331
1811
|
"type": "array",
|
|
1332
1812
|
"minItems": 1,
|
|
@@ -1343,21 +1823,337 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1343
1823
|
"type": { "const": "file" },
|
|
1344
1824
|
"label": { "type": "string" },
|
|
1345
1825
|
"description": { "type": "string" },
|
|
1826
|
+
"placeholder": { "type": "string" },
|
|
1827
|
+
"answer_guidelines": { "type": "string" },
|
|
1828
|
+
"reference_id": { "type": "string" },
|
|
1346
1829
|
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
1347
1830
|
},
|
|
1348
1831
|
"additionalProperties": false
|
|
1349
1832
|
},
|
|
1350
|
-
"
|
|
1351
|
-
{ "$ref": "#/$defs/arrayItemString" },
|
|
1352
|
-
{ "$ref": "#/$defs/arrayItemNumber" },
|
|
1353
|
-
{ "$ref": "#/$defs/arrayItemBoolean" },
|
|
1354
|
-
{ "$ref": "#/$defs/arrayItemDate" },
|
|
1355
|
-
{ "$ref": "#/$defs/arrayItemSelect" },
|
|
1356
|
-
{ "$ref": "#/$defs/arrayItemFile" }
|
|
1357
|
-
] },
|
|
1358
|
-
"stringField": {
|
|
1833
|
+
"arrayItemBlockchain": {
|
|
1359
1834
|
"type": "object",
|
|
1360
|
-
"required": [
|
|
1835
|
+
"required": ["type", "label"],
|
|
1836
|
+
"properties": {
|
|
1837
|
+
"type": { "const": "blockchain" },
|
|
1838
|
+
"label": { "type": "string" },
|
|
1839
|
+
"description": { "type": "string" },
|
|
1840
|
+
"placeholder": { "type": "string" },
|
|
1841
|
+
"answer_guidelines": { "type": "string" },
|
|
1842
|
+
"reference_id": { "type": "string" },
|
|
1843
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
1844
|
+
},
|
|
1845
|
+
"additionalProperties": false
|
|
1846
|
+
},
|
|
1847
|
+
"arrayItemSustainability": {
|
|
1848
|
+
"type": "object",
|
|
1849
|
+
"required": ["type", "label"],
|
|
1850
|
+
"properties": {
|
|
1851
|
+
"type": { "const": "sustainability" },
|
|
1852
|
+
"label": { "type": "string" },
|
|
1853
|
+
"description": { "type": "string" },
|
|
1854
|
+
"placeholder": { "type": "string" },
|
|
1855
|
+
"answer_guidelines": { "type": "string" },
|
|
1856
|
+
"reference_id": { "type": "string" },
|
|
1857
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
1858
|
+
},
|
|
1859
|
+
"additionalProperties": false
|
|
1860
|
+
},
|
|
1861
|
+
"arrayItemMultiselect": {
|
|
1862
|
+
"type": "object",
|
|
1863
|
+
"required": [
|
|
1864
|
+
"type",
|
|
1865
|
+
"label",
|
|
1866
|
+
"options"
|
|
1867
|
+
],
|
|
1868
|
+
"properties": {
|
|
1869
|
+
"type": { "const": "multiselect" },
|
|
1870
|
+
"label": { "type": "string" },
|
|
1871
|
+
"description": { "type": "string" },
|
|
1872
|
+
"placeholder": { "type": "string" },
|
|
1873
|
+
"answer_guidelines": { "type": "string" },
|
|
1874
|
+
"reference_id": { "type": "string" },
|
|
1875
|
+
"options": {
|
|
1876
|
+
"type": "array",
|
|
1877
|
+
"minItems": 1,
|
|
1878
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
1879
|
+
},
|
|
1880
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
1881
|
+
},
|
|
1882
|
+
"additionalProperties": false
|
|
1883
|
+
},
|
|
1884
|
+
"arrayItemStatic": {
|
|
1885
|
+
"type": "object",
|
|
1886
|
+
"required": ["type", "text"],
|
|
1887
|
+
"properties": {
|
|
1888
|
+
"type": { "const": "static" },
|
|
1889
|
+
"text": { "type": "string" },
|
|
1890
|
+
"label": { "type": "string" },
|
|
1891
|
+
"description": { "type": "string" },
|
|
1892
|
+
"placeholder": { "type": "string" },
|
|
1893
|
+
"answer_guidelines": { "type": "string" },
|
|
1894
|
+
"reference_id": { "type": "string" }
|
|
1895
|
+
},
|
|
1896
|
+
"additionalProperties": false
|
|
1897
|
+
},
|
|
1898
|
+
"arrayItem": { "oneOf": [
|
|
1899
|
+
{ "$ref": "#/$defs/arrayItemString" },
|
|
1900
|
+
{ "$ref": "#/$defs/arrayItemNumber" },
|
|
1901
|
+
{ "$ref": "#/$defs/arrayItemBoolean" },
|
|
1902
|
+
{ "$ref": "#/$defs/arrayItemDate" },
|
|
1903
|
+
{ "$ref": "#/$defs/arrayItemSelect" },
|
|
1904
|
+
{ "$ref": "#/$defs/arrayItemFile" },
|
|
1905
|
+
{ "$ref": "#/$defs/arrayItemBlockchain" },
|
|
1906
|
+
{ "$ref": "#/$defs/arrayItemSustainability" },
|
|
1907
|
+
{ "$ref": "#/$defs/arrayItemMultiselect" },
|
|
1908
|
+
{ "$ref": "#/$defs/arrayItemStatic" }
|
|
1909
|
+
] },
|
|
1910
|
+
"arrayObjItemString": {
|
|
1911
|
+
"type": "object",
|
|
1912
|
+
"required": [
|
|
1913
|
+
"id",
|
|
1914
|
+
"type",
|
|
1915
|
+
"label"
|
|
1916
|
+
],
|
|
1917
|
+
"properties": {
|
|
1918
|
+
"id": {
|
|
1919
|
+
"type": "integer",
|
|
1920
|
+
"minimum": 1
|
|
1921
|
+
},
|
|
1922
|
+
"type": { "const": "string" },
|
|
1923
|
+
"label": { "type": "string" },
|
|
1924
|
+
"description": { "type": "string" },
|
|
1925
|
+
"placeholder": { "type": "string" },
|
|
1926
|
+
"answer_guidelines": { "type": "string" },
|
|
1927
|
+
"reference_id": { "type": "string" },
|
|
1928
|
+
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1929
|
+
},
|
|
1930
|
+
"additionalProperties": false
|
|
1931
|
+
},
|
|
1932
|
+
"arrayObjItemNumber": {
|
|
1933
|
+
"type": "object",
|
|
1934
|
+
"required": [
|
|
1935
|
+
"id",
|
|
1936
|
+
"type",
|
|
1937
|
+
"label"
|
|
1938
|
+
],
|
|
1939
|
+
"properties": {
|
|
1940
|
+
"id": {
|
|
1941
|
+
"type": "integer",
|
|
1942
|
+
"minimum": 1
|
|
1943
|
+
},
|
|
1944
|
+
"type": { "const": "number" },
|
|
1945
|
+
"label": { "type": "string" },
|
|
1946
|
+
"description": { "type": "string" },
|
|
1947
|
+
"placeholder": { "type": "string" },
|
|
1948
|
+
"answer_guidelines": { "type": "string" },
|
|
1949
|
+
"reference_id": { "type": "string" },
|
|
1950
|
+
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1951
|
+
},
|
|
1952
|
+
"additionalProperties": false
|
|
1953
|
+
},
|
|
1954
|
+
"arrayObjItemBoolean": {
|
|
1955
|
+
"type": "object",
|
|
1956
|
+
"required": [
|
|
1957
|
+
"id",
|
|
1958
|
+
"type",
|
|
1959
|
+
"label"
|
|
1960
|
+
],
|
|
1961
|
+
"properties": {
|
|
1962
|
+
"id": {
|
|
1963
|
+
"type": "integer",
|
|
1964
|
+
"minimum": 1
|
|
1965
|
+
},
|
|
1966
|
+
"type": { "const": "boolean" },
|
|
1967
|
+
"label": { "type": "string" },
|
|
1968
|
+
"description": { "type": "string" },
|
|
1969
|
+
"placeholder": { "type": "string" },
|
|
1970
|
+
"answer_guidelines": { "type": "string" },
|
|
1971
|
+
"reference_id": { "type": "string" },
|
|
1972
|
+
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1973
|
+
},
|
|
1974
|
+
"additionalProperties": false
|
|
1975
|
+
},
|
|
1976
|
+
"arrayObjItemDate": {
|
|
1977
|
+
"type": "object",
|
|
1978
|
+
"required": [
|
|
1979
|
+
"id",
|
|
1980
|
+
"type",
|
|
1981
|
+
"label"
|
|
1982
|
+
],
|
|
1983
|
+
"properties": {
|
|
1984
|
+
"id": {
|
|
1985
|
+
"type": "integer",
|
|
1986
|
+
"minimum": 1
|
|
1987
|
+
},
|
|
1988
|
+
"type": { "const": "date" },
|
|
1989
|
+
"label": { "type": "string" },
|
|
1990
|
+
"description": { "type": "string" },
|
|
1991
|
+
"placeholder": { "type": "string" },
|
|
1992
|
+
"answer_guidelines": { "type": "string" },
|
|
1993
|
+
"reference_id": { "type": "string" },
|
|
1994
|
+
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1995
|
+
},
|
|
1996
|
+
"additionalProperties": false
|
|
1997
|
+
},
|
|
1998
|
+
"arrayObjItemSelect": {
|
|
1999
|
+
"type": "object",
|
|
2000
|
+
"required": [
|
|
2001
|
+
"id",
|
|
2002
|
+
"type",
|
|
2003
|
+
"label",
|
|
2004
|
+
"options"
|
|
2005
|
+
],
|
|
2006
|
+
"properties": {
|
|
2007
|
+
"id": {
|
|
2008
|
+
"type": "integer",
|
|
2009
|
+
"minimum": 1
|
|
2010
|
+
},
|
|
2011
|
+
"type": { "const": "select" },
|
|
2012
|
+
"label": { "type": "string" },
|
|
2013
|
+
"description": { "type": "string" },
|
|
2014
|
+
"placeholder": { "type": "string" },
|
|
2015
|
+
"answer_guidelines": { "type": "string" },
|
|
2016
|
+
"reference_id": { "type": "string" },
|
|
2017
|
+
"options": {
|
|
2018
|
+
"type": "array",
|
|
2019
|
+
"minItems": 1,
|
|
2020
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
2021
|
+
},
|
|
2022
|
+
"validation": { "$ref": "#/$defs/selectValidation" }
|
|
2023
|
+
},
|
|
2024
|
+
"additionalProperties": false
|
|
2025
|
+
},
|
|
2026
|
+
"arrayObjItemMultiselect": {
|
|
2027
|
+
"type": "object",
|
|
2028
|
+
"required": [
|
|
2029
|
+
"id",
|
|
2030
|
+
"type",
|
|
2031
|
+
"label",
|
|
2032
|
+
"options"
|
|
2033
|
+
],
|
|
2034
|
+
"properties": {
|
|
2035
|
+
"id": {
|
|
2036
|
+
"type": "integer",
|
|
2037
|
+
"minimum": 1
|
|
2038
|
+
},
|
|
2039
|
+
"type": { "const": "multiselect" },
|
|
2040
|
+
"label": { "type": "string" },
|
|
2041
|
+
"description": { "type": "string" },
|
|
2042
|
+
"placeholder": { "type": "string" },
|
|
2043
|
+
"answer_guidelines": { "type": "string" },
|
|
2044
|
+
"reference_id": { "type": "string" },
|
|
2045
|
+
"options": {
|
|
2046
|
+
"type": "array",
|
|
2047
|
+
"minItems": 1,
|
|
2048
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
2049
|
+
},
|
|
2050
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
2051
|
+
},
|
|
2052
|
+
"additionalProperties": false
|
|
2053
|
+
},
|
|
2054
|
+
"arrayObjItemFile": {
|
|
2055
|
+
"type": "object",
|
|
2056
|
+
"required": [
|
|
2057
|
+
"id",
|
|
2058
|
+
"type",
|
|
2059
|
+
"label"
|
|
2060
|
+
],
|
|
2061
|
+
"properties": {
|
|
2062
|
+
"id": {
|
|
2063
|
+
"type": "integer",
|
|
2064
|
+
"minimum": 1
|
|
2065
|
+
},
|
|
2066
|
+
"type": { "const": "file" },
|
|
2067
|
+
"label": { "type": "string" },
|
|
2068
|
+
"description": { "type": "string" },
|
|
2069
|
+
"placeholder": { "type": "string" },
|
|
2070
|
+
"answer_guidelines": { "type": "string" },
|
|
2071
|
+
"reference_id": { "type": "string" },
|
|
2072
|
+
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
2073
|
+
},
|
|
2074
|
+
"additionalProperties": false
|
|
2075
|
+
},
|
|
2076
|
+
"arrayObjItemBlockchain": {
|
|
2077
|
+
"type": "object",
|
|
2078
|
+
"required": [
|
|
2079
|
+
"id",
|
|
2080
|
+
"type",
|
|
2081
|
+
"label"
|
|
2082
|
+
],
|
|
2083
|
+
"properties": {
|
|
2084
|
+
"id": {
|
|
2085
|
+
"type": "integer",
|
|
2086
|
+
"minimum": 1
|
|
2087
|
+
},
|
|
2088
|
+
"type": { "const": "blockchain" },
|
|
2089
|
+
"label": { "type": "string" },
|
|
2090
|
+
"description": { "type": "string" },
|
|
2091
|
+
"placeholder": { "type": "string" },
|
|
2092
|
+
"answer_guidelines": { "type": "string" },
|
|
2093
|
+
"reference_id": { "type": "string" },
|
|
2094
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
2095
|
+
},
|
|
2096
|
+
"additionalProperties": false
|
|
2097
|
+
},
|
|
2098
|
+
"arrayObjItemSustainability": {
|
|
2099
|
+
"type": "object",
|
|
2100
|
+
"required": [
|
|
2101
|
+
"id",
|
|
2102
|
+
"type",
|
|
2103
|
+
"label"
|
|
2104
|
+
],
|
|
2105
|
+
"properties": {
|
|
2106
|
+
"id": {
|
|
2107
|
+
"type": "integer",
|
|
2108
|
+
"minimum": 1
|
|
2109
|
+
},
|
|
2110
|
+
"type": { "const": "sustainability" },
|
|
2111
|
+
"label": { "type": "string" },
|
|
2112
|
+
"description": { "type": "string" },
|
|
2113
|
+
"placeholder": { "type": "string" },
|
|
2114
|
+
"answer_guidelines": { "type": "string" },
|
|
2115
|
+
"reference_id": { "type": "string" },
|
|
2116
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
2117
|
+
},
|
|
2118
|
+
"additionalProperties": false
|
|
2119
|
+
},
|
|
2120
|
+
"arrayObjItemStatic": {
|
|
2121
|
+
"type": "object",
|
|
2122
|
+
"required": [
|
|
2123
|
+
"id",
|
|
2124
|
+
"type",
|
|
2125
|
+
"text"
|
|
2126
|
+
],
|
|
2127
|
+
"properties": {
|
|
2128
|
+
"id": {
|
|
2129
|
+
"type": "integer",
|
|
2130
|
+
"minimum": 1
|
|
2131
|
+
},
|
|
2132
|
+
"type": { "const": "static" },
|
|
2133
|
+
"text": { "type": "string" },
|
|
2134
|
+
"label": { "type": "string" },
|
|
2135
|
+
"description": { "type": "string" },
|
|
2136
|
+
"placeholder": { "type": "string" },
|
|
2137
|
+
"answer_guidelines": { "type": "string" },
|
|
2138
|
+
"reference_id": { "type": "string" }
|
|
2139
|
+
},
|
|
2140
|
+
"additionalProperties": false
|
|
2141
|
+
},
|
|
2142
|
+
"arrayObjItem": { "oneOf": [
|
|
2143
|
+
{ "$ref": "#/$defs/arrayObjItemString" },
|
|
2144
|
+
{ "$ref": "#/$defs/arrayObjItemNumber" },
|
|
2145
|
+
{ "$ref": "#/$defs/arrayObjItemBoolean" },
|
|
2146
|
+
{ "$ref": "#/$defs/arrayObjItemDate" },
|
|
2147
|
+
{ "$ref": "#/$defs/arrayObjItemSelect" },
|
|
2148
|
+
{ "$ref": "#/$defs/arrayObjItemMultiselect" },
|
|
2149
|
+
{ "$ref": "#/$defs/arrayObjItemFile" },
|
|
2150
|
+
{ "$ref": "#/$defs/arrayObjItemBlockchain" },
|
|
2151
|
+
{ "$ref": "#/$defs/arrayObjItemSustainability" },
|
|
2152
|
+
{ "$ref": "#/$defs/arrayObjItemStatic" }
|
|
2153
|
+
] },
|
|
2154
|
+
"stringField": {
|
|
2155
|
+
"type": "object",
|
|
2156
|
+
"required": [
|
|
1361
2157
|
"id",
|
|
1362
2158
|
"type",
|
|
1363
2159
|
"label"
|
|
@@ -1370,6 +2166,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1370
2166
|
"type": { "const": "string" },
|
|
1371
2167
|
"label": { "type": "string" },
|
|
1372
2168
|
"description": { "type": "string" },
|
|
2169
|
+
"placeholder": { "type": "string" },
|
|
2170
|
+
"answer_guidelines": { "type": "string" },
|
|
2171
|
+
"reference_id": { "type": "string" },
|
|
1373
2172
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1374
2173
|
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1375
2174
|
},
|
|
@@ -1390,6 +2189,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1390
2189
|
"type": { "const": "number" },
|
|
1391
2190
|
"label": { "type": "string" },
|
|
1392
2191
|
"description": { "type": "string" },
|
|
2192
|
+
"placeholder": { "type": "string" },
|
|
2193
|
+
"answer_guidelines": { "type": "string" },
|
|
2194
|
+
"reference_id": { "type": "string" },
|
|
1393
2195
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1394
2196
|
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1395
2197
|
},
|
|
@@ -1410,6 +2212,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1410
2212
|
"type": { "const": "boolean" },
|
|
1411
2213
|
"label": { "type": "string" },
|
|
1412
2214
|
"description": { "type": "string" },
|
|
2215
|
+
"placeholder": { "type": "string" },
|
|
2216
|
+
"answer_guidelines": { "type": "string" },
|
|
2217
|
+
"reference_id": { "type": "string" },
|
|
1413
2218
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1414
2219
|
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1415
2220
|
},
|
|
@@ -1430,6 +2235,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1430
2235
|
"type": { "const": "date" },
|
|
1431
2236
|
"label": { "type": "string" },
|
|
1432
2237
|
"description": { "type": "string" },
|
|
2238
|
+
"placeholder": { "type": "string" },
|
|
2239
|
+
"answer_guidelines": { "type": "string" },
|
|
2240
|
+
"reference_id": { "type": "string" },
|
|
1433
2241
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1434
2242
|
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1435
2243
|
},
|
|
@@ -1451,6 +2259,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1451
2259
|
"type": { "const": "select" },
|
|
1452
2260
|
"label": { "type": "string" },
|
|
1453
2261
|
"description": { "type": "string" },
|
|
2262
|
+
"placeholder": { "type": "string" },
|
|
2263
|
+
"answer_guidelines": { "type": "string" },
|
|
2264
|
+
"reference_id": { "type": "string" },
|
|
1454
2265
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1455
2266
|
"options": {
|
|
1456
2267
|
"type": "array",
|
|
@@ -1461,6 +2272,58 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1461
2272
|
},
|
|
1462
2273
|
"additionalProperties": false
|
|
1463
2274
|
},
|
|
2275
|
+
"multiselectField": {
|
|
2276
|
+
"type": "object",
|
|
2277
|
+
"required": [
|
|
2278
|
+
"id",
|
|
2279
|
+
"type",
|
|
2280
|
+
"label",
|
|
2281
|
+
"options"
|
|
2282
|
+
],
|
|
2283
|
+
"properties": {
|
|
2284
|
+
"id": {
|
|
2285
|
+
"type": "integer",
|
|
2286
|
+
"minimum": 1
|
|
2287
|
+
},
|
|
2288
|
+
"type": { "const": "multiselect" },
|
|
2289
|
+
"label": { "type": "string" },
|
|
2290
|
+
"description": { "type": "string" },
|
|
2291
|
+
"placeholder": { "type": "string" },
|
|
2292
|
+
"answer_guidelines": { "type": "string" },
|
|
2293
|
+
"reference_id": { "type": "string" },
|
|
2294
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2295
|
+
"options": {
|
|
2296
|
+
"type": "array",
|
|
2297
|
+
"minItems": 1,
|
|
2298
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
2299
|
+
},
|
|
2300
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
2301
|
+
},
|
|
2302
|
+
"additionalProperties": false
|
|
2303
|
+
},
|
|
2304
|
+
"staticField": {
|
|
2305
|
+
"type": "object",
|
|
2306
|
+
"required": [
|
|
2307
|
+
"id",
|
|
2308
|
+
"type",
|
|
2309
|
+
"text"
|
|
2310
|
+
],
|
|
2311
|
+
"properties": {
|
|
2312
|
+
"id": {
|
|
2313
|
+
"type": "integer",
|
|
2314
|
+
"minimum": 1
|
|
2315
|
+
},
|
|
2316
|
+
"type": { "const": "static" },
|
|
2317
|
+
"text": { "type": "string" },
|
|
2318
|
+
"label": { "type": "string" },
|
|
2319
|
+
"description": { "type": "string" },
|
|
2320
|
+
"placeholder": { "type": "string" },
|
|
2321
|
+
"answer_guidelines": { "type": "string" },
|
|
2322
|
+
"reference_id": { "type": "string" },
|
|
2323
|
+
"condition": { "$ref": "#/$defs/condition" }
|
|
2324
|
+
},
|
|
2325
|
+
"additionalProperties": false
|
|
2326
|
+
},
|
|
1464
2327
|
"arrayField": {
|
|
1465
2328
|
"type": "object",
|
|
1466
2329
|
"required": [
|
|
@@ -1477,12 +2340,48 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1477
2340
|
"type": { "const": "array" },
|
|
1478
2341
|
"label": { "type": "string" },
|
|
1479
2342
|
"description": { "type": "string" },
|
|
2343
|
+
"placeholder": { "type": "string" },
|
|
2344
|
+
"answer_guidelines": { "type": "string" },
|
|
2345
|
+
"reference_id": { "type": "string" },
|
|
1480
2346
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1481
2347
|
"item": { "$ref": "#/$defs/arrayItem" },
|
|
1482
2348
|
"validation": { "$ref": "#/$defs/arrayValidation" }
|
|
1483
2349
|
},
|
|
1484
2350
|
"additionalProperties": false
|
|
1485
2351
|
},
|
|
2352
|
+
"arrayObjField": {
|
|
2353
|
+
"type": "object",
|
|
2354
|
+
"required": [
|
|
2355
|
+
"id",
|
|
2356
|
+
"type",
|
|
2357
|
+
"label",
|
|
2358
|
+
"items"
|
|
2359
|
+
],
|
|
2360
|
+
"properties": {
|
|
2361
|
+
"id": {
|
|
2362
|
+
"type": "integer",
|
|
2363
|
+
"minimum": 1
|
|
2364
|
+
},
|
|
2365
|
+
"type": { "const": "array_obj" },
|
|
2366
|
+
"label": { "type": "string" },
|
|
2367
|
+
"description": { "type": "string" },
|
|
2368
|
+
"placeholder": { "type": "string" },
|
|
2369
|
+
"answer_guidelines": { "type": "string" },
|
|
2370
|
+
"reference_id": { "type": "string" },
|
|
2371
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2372
|
+
"items": {
|
|
2373
|
+
"type": "array",
|
|
2374
|
+
"minItems": 1,
|
|
2375
|
+
"items": { "$ref": "#/$defs/arrayObjItem" }
|
|
2376
|
+
},
|
|
2377
|
+
"kind": {
|
|
2378
|
+
"type": "string",
|
|
2379
|
+
"enum": ["list", "table"]
|
|
2380
|
+
},
|
|
2381
|
+
"validation": { "$ref": "#/$defs/arrayObjValidation" }
|
|
2382
|
+
},
|
|
2383
|
+
"additionalProperties": false
|
|
2384
|
+
},
|
|
1486
2385
|
"fileField": {
|
|
1487
2386
|
"type": "object",
|
|
1488
2387
|
"required": [
|
|
@@ -1498,19 +2397,73 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1498
2397
|
"type": { "const": "file" },
|
|
1499
2398
|
"label": { "type": "string" },
|
|
1500
2399
|
"description": { "type": "string" },
|
|
2400
|
+
"placeholder": { "type": "string" },
|
|
2401
|
+
"answer_guidelines": { "type": "string" },
|
|
2402
|
+
"reference_id": { "type": "string" },
|
|
1501
2403
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1502
2404
|
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
1503
2405
|
},
|
|
1504
2406
|
"additionalProperties": false
|
|
1505
2407
|
},
|
|
2408
|
+
"blockchainField": {
|
|
2409
|
+
"type": "object",
|
|
2410
|
+
"required": [
|
|
2411
|
+
"id",
|
|
2412
|
+
"type",
|
|
2413
|
+
"label"
|
|
2414
|
+
],
|
|
2415
|
+
"properties": {
|
|
2416
|
+
"id": {
|
|
2417
|
+
"type": "integer",
|
|
2418
|
+
"minimum": 1
|
|
2419
|
+
},
|
|
2420
|
+
"type": { "const": "blockchain" },
|
|
2421
|
+
"label": { "type": "string" },
|
|
2422
|
+
"description": { "type": "string" },
|
|
2423
|
+
"placeholder": { "type": "string" },
|
|
2424
|
+
"answer_guidelines": { "type": "string" },
|
|
2425
|
+
"reference_id": { "type": "string" },
|
|
2426
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2427
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
2428
|
+
},
|
|
2429
|
+
"additionalProperties": false
|
|
2430
|
+
},
|
|
2431
|
+
"sustainabilityField": {
|
|
2432
|
+
"type": "object",
|
|
2433
|
+
"required": [
|
|
2434
|
+
"id",
|
|
2435
|
+
"type",
|
|
2436
|
+
"label"
|
|
2437
|
+
],
|
|
2438
|
+
"properties": {
|
|
2439
|
+
"id": {
|
|
2440
|
+
"type": "integer",
|
|
2441
|
+
"minimum": 1
|
|
2442
|
+
},
|
|
2443
|
+
"type": { "const": "sustainability" },
|
|
2444
|
+
"label": { "type": "string" },
|
|
2445
|
+
"description": { "type": "string" },
|
|
2446
|
+
"placeholder": { "type": "string" },
|
|
2447
|
+
"answer_guidelines": { "type": "string" },
|
|
2448
|
+
"reference_id": { "type": "string" },
|
|
2449
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2450
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
2451
|
+
},
|
|
2452
|
+
"additionalProperties": false
|
|
2453
|
+
},
|
|
1506
2454
|
"fieldItem": { "oneOf": [
|
|
1507
2455
|
{ "$ref": "#/$defs/stringField" },
|
|
1508
2456
|
{ "$ref": "#/$defs/numberField" },
|
|
1509
2457
|
{ "$ref": "#/$defs/booleanField" },
|
|
1510
2458
|
{ "$ref": "#/$defs/dateField" },
|
|
1511
2459
|
{ "$ref": "#/$defs/selectField" },
|
|
2460
|
+
{ "$ref": "#/$defs/multiselectField" },
|
|
1512
2461
|
{ "$ref": "#/$defs/arrayField" },
|
|
1513
|
-
{ "$ref": "#/$defs/
|
|
2462
|
+
{ "$ref": "#/$defs/arrayObjField" },
|
|
2463
|
+
{ "$ref": "#/$defs/fileField" },
|
|
2464
|
+
{ "$ref": "#/$defs/staticField" },
|
|
2465
|
+
{ "$ref": "#/$defs/blockchainField" },
|
|
2466
|
+
{ "$ref": "#/$defs/sustainabilityField" }
|
|
1514
2467
|
] },
|
|
1515
2468
|
"section": {
|
|
1516
2469
|
"type": "object",
|
|
@@ -1528,6 +2481,9 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1528
2481
|
"type": { "const": "section" },
|
|
1529
2482
|
"title": { "type": "string" },
|
|
1530
2483
|
"description": { "type": "string" },
|
|
2484
|
+
"placeholder": { "type": "string" },
|
|
2485
|
+
"answer_guidelines": { "type": "string" },
|
|
2486
|
+
"reference_id": { "type": "string" },
|
|
1531
2487
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1532
2488
|
"content": {
|
|
1533
2489
|
"type": "array",
|
|
@@ -1540,6 +2496,228 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1540
2496
|
"contentItem": { "oneOf": [{ "$ref": "#/$defs/fieldItem" }, { "$ref": "#/$defs/section" }] }
|
|
1541
2497
|
}
|
|
1542
2498
|
});
|
|
2499
|
+
const itemRequiredProperties = new Map([
|
|
2500
|
+
["string", new Set([
|
|
2501
|
+
"id",
|
|
2502
|
+
"type",
|
|
2503
|
+
"label"
|
|
2504
|
+
])],
|
|
2505
|
+
["number", new Set([
|
|
2506
|
+
"id",
|
|
2507
|
+
"type",
|
|
2508
|
+
"label"
|
|
2509
|
+
])],
|
|
2510
|
+
["boolean", new Set([
|
|
2511
|
+
"id",
|
|
2512
|
+
"type",
|
|
2513
|
+
"label"
|
|
2514
|
+
])],
|
|
2515
|
+
["date", new Set([
|
|
2516
|
+
"id",
|
|
2517
|
+
"type",
|
|
2518
|
+
"label"
|
|
2519
|
+
])],
|
|
2520
|
+
["select", new Set([
|
|
2521
|
+
"id",
|
|
2522
|
+
"type",
|
|
2523
|
+
"label",
|
|
2524
|
+
"options"
|
|
2525
|
+
])],
|
|
2526
|
+
["multiselect", new Set([
|
|
2527
|
+
"id",
|
|
2528
|
+
"type",
|
|
2529
|
+
"label",
|
|
2530
|
+
"options"
|
|
2531
|
+
])],
|
|
2532
|
+
["array", new Set([
|
|
2533
|
+
"id",
|
|
2534
|
+
"type",
|
|
2535
|
+
"label",
|
|
2536
|
+
"item"
|
|
2537
|
+
])],
|
|
2538
|
+
["array_obj", new Set([
|
|
2539
|
+
"id",
|
|
2540
|
+
"type",
|
|
2541
|
+
"label",
|
|
2542
|
+
"items"
|
|
2543
|
+
])],
|
|
2544
|
+
["file", new Set([
|
|
2545
|
+
"id",
|
|
2546
|
+
"type",
|
|
2547
|
+
"label"
|
|
2548
|
+
])],
|
|
2549
|
+
["blockchain", new Set([
|
|
2550
|
+
"id",
|
|
2551
|
+
"type",
|
|
2552
|
+
"label"
|
|
2553
|
+
])],
|
|
2554
|
+
["sustainability", new Set([
|
|
2555
|
+
"id",
|
|
2556
|
+
"type",
|
|
2557
|
+
"label"
|
|
2558
|
+
])],
|
|
2559
|
+
["static", new Set([
|
|
2560
|
+
"id",
|
|
2561
|
+
"type",
|
|
2562
|
+
"text"
|
|
2563
|
+
])],
|
|
2564
|
+
["section", new Set([
|
|
2565
|
+
"id",
|
|
2566
|
+
"type",
|
|
2567
|
+
"title",
|
|
2568
|
+
"content"
|
|
2569
|
+
])]
|
|
2570
|
+
]);
|
|
2571
|
+
const itemAllowedProperties = new Map([
|
|
2572
|
+
["string", new Set([
|
|
2573
|
+
"id",
|
|
2574
|
+
"type",
|
|
2575
|
+
"label",
|
|
2576
|
+
"description",
|
|
2577
|
+
"placeholder",
|
|
2578
|
+
"answer_guidelines",
|
|
2579
|
+
"reference_id",
|
|
2580
|
+
"condition",
|
|
2581
|
+
"validation"
|
|
2582
|
+
])],
|
|
2583
|
+
["number", new Set([
|
|
2584
|
+
"id",
|
|
2585
|
+
"type",
|
|
2586
|
+
"label",
|
|
2587
|
+
"description",
|
|
2588
|
+
"placeholder",
|
|
2589
|
+
"answer_guidelines",
|
|
2590
|
+
"reference_id",
|
|
2591
|
+
"condition",
|
|
2592
|
+
"validation"
|
|
2593
|
+
])],
|
|
2594
|
+
["boolean", new Set([
|
|
2595
|
+
"id",
|
|
2596
|
+
"type",
|
|
2597
|
+
"label",
|
|
2598
|
+
"description",
|
|
2599
|
+
"placeholder",
|
|
2600
|
+
"answer_guidelines",
|
|
2601
|
+
"reference_id",
|
|
2602
|
+
"condition",
|
|
2603
|
+
"validation"
|
|
2604
|
+
])],
|
|
2605
|
+
["date", new Set([
|
|
2606
|
+
"id",
|
|
2607
|
+
"type",
|
|
2608
|
+
"label",
|
|
2609
|
+
"description",
|
|
2610
|
+
"placeholder",
|
|
2611
|
+
"answer_guidelines",
|
|
2612
|
+
"reference_id",
|
|
2613
|
+
"condition",
|
|
2614
|
+
"validation"
|
|
2615
|
+
])],
|
|
2616
|
+
["select", new Set([
|
|
2617
|
+
"id",
|
|
2618
|
+
"type",
|
|
2619
|
+
"label",
|
|
2620
|
+
"description",
|
|
2621
|
+
"placeholder",
|
|
2622
|
+
"answer_guidelines",
|
|
2623
|
+
"reference_id",
|
|
2624
|
+
"condition",
|
|
2625
|
+
"options",
|
|
2626
|
+
"validation"
|
|
2627
|
+
])],
|
|
2628
|
+
["multiselect", new Set([
|
|
2629
|
+
"id",
|
|
2630
|
+
"type",
|
|
2631
|
+
"label",
|
|
2632
|
+
"description",
|
|
2633
|
+
"placeholder",
|
|
2634
|
+
"answer_guidelines",
|
|
2635
|
+
"reference_id",
|
|
2636
|
+
"condition",
|
|
2637
|
+
"options",
|
|
2638
|
+
"validation"
|
|
2639
|
+
])],
|
|
2640
|
+
["array", new Set([
|
|
2641
|
+
"id",
|
|
2642
|
+
"type",
|
|
2643
|
+
"label",
|
|
2644
|
+
"description",
|
|
2645
|
+
"placeholder",
|
|
2646
|
+
"answer_guidelines",
|
|
2647
|
+
"reference_id",
|
|
2648
|
+
"condition",
|
|
2649
|
+
"item",
|
|
2650
|
+
"validation"
|
|
2651
|
+
])],
|
|
2652
|
+
["array_obj", new Set([
|
|
2653
|
+
"id",
|
|
2654
|
+
"type",
|
|
2655
|
+
"label",
|
|
2656
|
+
"description",
|
|
2657
|
+
"placeholder",
|
|
2658
|
+
"answer_guidelines",
|
|
2659
|
+
"reference_id",
|
|
2660
|
+
"condition",
|
|
2661
|
+
"items",
|
|
2662
|
+
"kind",
|
|
2663
|
+
"validation"
|
|
2664
|
+
])],
|
|
2665
|
+
["file", new Set([
|
|
2666
|
+
"id",
|
|
2667
|
+
"type",
|
|
2668
|
+
"label",
|
|
2669
|
+
"description",
|
|
2670
|
+
"placeholder",
|
|
2671
|
+
"answer_guidelines",
|
|
2672
|
+
"reference_id",
|
|
2673
|
+
"condition",
|
|
2674
|
+
"validation"
|
|
2675
|
+
])],
|
|
2676
|
+
["blockchain", new Set([
|
|
2677
|
+
"id",
|
|
2678
|
+
"type",
|
|
2679
|
+
"label",
|
|
2680
|
+
"description",
|
|
2681
|
+
"placeholder",
|
|
2682
|
+
"answer_guidelines",
|
|
2683
|
+
"reference_id",
|
|
2684
|
+
"condition",
|
|
2685
|
+
"validation"
|
|
2686
|
+
])],
|
|
2687
|
+
["sustainability", new Set([
|
|
2688
|
+
"id",
|
|
2689
|
+
"type",
|
|
2690
|
+
"label",
|
|
2691
|
+
"description",
|
|
2692
|
+
"placeholder",
|
|
2693
|
+
"answer_guidelines",
|
|
2694
|
+
"reference_id",
|
|
2695
|
+
"condition",
|
|
2696
|
+
"validation"
|
|
2697
|
+
])],
|
|
2698
|
+
["static", new Set([
|
|
2699
|
+
"id",
|
|
2700
|
+
"type",
|
|
2701
|
+
"text",
|
|
2702
|
+
"label",
|
|
2703
|
+
"description",
|
|
2704
|
+
"placeholder",
|
|
2705
|
+
"answer_guidelines",
|
|
2706
|
+
"reference_id",
|
|
2707
|
+
"condition"
|
|
2708
|
+
])],
|
|
2709
|
+
["section", new Set([
|
|
2710
|
+
"id",
|
|
2711
|
+
"type",
|
|
2712
|
+
"title",
|
|
2713
|
+
"description",
|
|
2714
|
+
"placeholder",
|
|
2715
|
+
"answer_guidelines",
|
|
2716
|
+
"reference_id",
|
|
2717
|
+
"condition",
|
|
2718
|
+
"content"
|
|
2719
|
+
])]
|
|
2720
|
+
]);
|
|
1543
2721
|
/**
|
|
1544
2722
|
* Validates form definitions at both the structural (JSON Schema) and
|
|
1545
2723
|
* semantic levels.
|
|
@@ -1553,12 +2731,17 @@ const validateFn = new ajv_dist_2020.default({ allErrors: true }).compile({
|
|
|
1553
2731
|
* ### Semantic validation (`validate`)
|
|
1554
2732
|
* Checks for logical issues that go beyond JSON schema validity:
|
|
1555
2733
|
* 1. **Duplicate IDs** (`DUPLICATE_ID`) -- every content item id must be unique.
|
|
2734
|
+
* `array_obj` sub-field ids share that id space and are checked with it.
|
|
1556
2735
|
* 2. **Nesting depth** (`NESTING_DEPTH`) -- sections may not be nested more
|
|
1557
2736
|
* than 3 levels deep.
|
|
1558
2737
|
* 3. **Unknown field references** (`UNKNOWN_FIELD_REF`) -- conditions must
|
|
1559
2738
|
* only reference field ids that exist in the registry.
|
|
1560
|
-
* 4. **Condition references
|
|
1561
|
-
* must not reference section
|
|
2739
|
+
* 4. **Condition references a valueless item** (`CONDITION_REFS_SECTION`,
|
|
2740
|
+
* `CONDITION_REFS_STATIC`) -- conditions must not reference section or
|
|
2741
|
+
* static ids, because neither holds a value.
|
|
2742
|
+
* 4b. **Condition references an `array_obj` sub-field** (`CONDITION_REFS_ARRAY_OBJ_ITEM`)
|
|
2743
|
+
* -- a sub-field holds one value per row, so a form-level condition cannot
|
|
2744
|
+
* say which row it means.
|
|
1562
2745
|
* 5. **Constraint contradictions** (`INVALID_MIN_MAX`) -- e.g. `minLength > maxLength`,
|
|
1563
2746
|
* `min > max`, `minDate > maxDate` (absolute dates only), `minItems > maxItems`.
|
|
1564
2747
|
* 6. **Invalid regex** (`INVALID_REGEX`) -- string field `pattern` values must
|
|
@@ -1573,18 +2756,177 @@ var FormDefinitionValidator = class {
|
|
|
1573
2756
|
*/
|
|
1574
2757
|
validateSchema(input) {
|
|
1575
2758
|
if (validateFn(input)) return [];
|
|
1576
|
-
return (validateFn.errors ?? [])
|
|
2759
|
+
return this.formatSchemaErrors(validateFn.errors ?? [], input);
|
|
2760
|
+
}
|
|
2761
|
+
formatSchemaErrors(errors, input) {
|
|
2762
|
+
const issues = this.collectSchemaIssues(errors, input);
|
|
2763
|
+
const dedupedIssues = this.deduplicateSchemaIssues(issues);
|
|
2764
|
+
const specificIssuePaths = dedupedIssues.filter((issue) => issue.keyword !== "oneOf").map((issue) => issue.path);
|
|
2765
|
+
return dedupedIssues.filter((issue) => !this.isRedundantOneOfIssue(issue, specificIssuePaths)).map((issue) => ({
|
|
2766
|
+
code: "SCHEMA_INVALID",
|
|
2767
|
+
message: issue.message,
|
|
2768
|
+
params: {
|
|
2769
|
+
path: issue.path,
|
|
2770
|
+
keyword: issue.keyword,
|
|
2771
|
+
...issue.property ? { property: issue.property } : {}
|
|
2772
|
+
}
|
|
2773
|
+
}));
|
|
2774
|
+
}
|
|
2775
|
+
collectSchemaIssues(errors, input) {
|
|
2776
|
+
const additionalByPath = /* @__PURE__ */ new Map();
|
|
2777
|
+
const issues = [];
|
|
2778
|
+
for (const err of errors) {
|
|
1577
2779
|
const path = err.instancePath || "/";
|
|
1578
|
-
|
|
1579
|
-
if (err.keyword === "additionalProperties")
|
|
1580
|
-
|
|
1581
|
-
|
|
2780
|
+
if (!this.shouldKeepSchemaError(err, input)) continue;
|
|
2781
|
+
if (err.keyword === "additionalProperties") {
|
|
2782
|
+
const property = err.params.additionalProperty;
|
|
2783
|
+
if (!property) continue;
|
|
2784
|
+
if (!this.shouldKeepAdditionalPropertyError(path, property, input)) continue;
|
|
2785
|
+
const properties = additionalByPath.get(path) ?? /* @__PURE__ */ new Set();
|
|
2786
|
+
properties.add(property);
|
|
2787
|
+
additionalByPath.set(path, properties);
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
issues.push(this.formatSchemaIssue(err));
|
|
2791
|
+
}
|
|
2792
|
+
for (const [path, properties] of additionalByPath) issues.push(this.formatAdditionalPropertiesIssue(path, [...properties].sort()));
|
|
2793
|
+
return issues;
|
|
2794
|
+
}
|
|
2795
|
+
shouldKeepSchemaError(err, input) {
|
|
2796
|
+
if (err.keyword === "const" && this.getLastPathSegment(err.instancePath) === "type") {
|
|
2797
|
+
const value = this.getValueAtPath(input, this.getParentPath(err.instancePath));
|
|
2798
|
+
return !(this.isRecord(value) && typeof value.type === "string" && itemAllowedProperties.has(value.type));
|
|
2799
|
+
}
|
|
2800
|
+
if (err.keyword !== "required") return true;
|
|
2801
|
+
const missingProperty = err.params.missingProperty;
|
|
2802
|
+
if (!missingProperty) return true;
|
|
2803
|
+
const value = this.getValueAtPath(input, err.instancePath);
|
|
2804
|
+
if (this.isRecord(value) && value.type === void 0 && this.isContentItemPath(err.instancePath)) return missingProperty === "id" || missingProperty === "type";
|
|
2805
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
2806
|
+
const requiredProperties = itemRequiredProperties.get(value.type);
|
|
2807
|
+
return requiredProperties ? requiredProperties.has(missingProperty) : true;
|
|
2808
|
+
}
|
|
2809
|
+
shouldKeepAdditionalPropertyError(path, property, input) {
|
|
2810
|
+
const value = this.getValueAtPath(input, path);
|
|
2811
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
2812
|
+
const allowedProperties = itemAllowedProperties.get(value.type);
|
|
2813
|
+
return allowedProperties ? !allowedProperties.has(property) : true;
|
|
2814
|
+
}
|
|
2815
|
+
formatSchemaIssue(err) {
|
|
2816
|
+
const path = err.instancePath || "/";
|
|
2817
|
+
switch (err.keyword) {
|
|
2818
|
+
case "required": {
|
|
2819
|
+
const property = err.params.missingProperty ?? "unknown";
|
|
2820
|
+
return {
|
|
2821
|
+
path,
|
|
2822
|
+
keyword: err.keyword,
|
|
2823
|
+
property,
|
|
2824
|
+
message: `${this.formatPath(path)} is missing required property "${property}".`
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
case "const": {
|
|
2828
|
+
const propertyPath = this.formatPath(path);
|
|
2829
|
+
const parentPath = this.getParentPath(path);
|
|
2830
|
+
const property = this.getLastPathSegment(path);
|
|
2831
|
+
return {
|
|
2832
|
+
path,
|
|
2833
|
+
keyword: err.keyword,
|
|
2834
|
+
property,
|
|
2835
|
+
message: property === "type" ? `${this.formatPath(parentPath)} has an invalid type.` : `${propertyPath} has an invalid value.`
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
case "type": {
|
|
2839
|
+
const params = err.params;
|
|
2840
|
+
return {
|
|
2841
|
+
path,
|
|
2842
|
+
keyword: err.keyword,
|
|
2843
|
+
property: this.getLastPathSegment(path),
|
|
2844
|
+
message: `${this.formatPath(path)} must be ${this.formatArticle(params.type)} ${params.type ?? "valid value"}.`
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
case "oneOf": return {
|
|
2848
|
+
path,
|
|
2849
|
+
keyword: err.keyword,
|
|
2850
|
+
message: `${this.formatPath(path)} is invalid.`
|
|
1582
2851
|
};
|
|
1583
|
-
return {
|
|
1584
|
-
|
|
1585
|
-
|
|
2852
|
+
default: return {
|
|
2853
|
+
path,
|
|
2854
|
+
keyword: err.keyword,
|
|
2855
|
+
property: this.getLastPathSegment(path),
|
|
2856
|
+
message: `${this.formatPath(path)} ${err.message ?? "is invalid"}.`
|
|
1586
2857
|
};
|
|
1587
|
-
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
formatAdditionalPropertiesIssue(path, properties) {
|
|
2861
|
+
const propertyList = properties.map((property) => `"${property}"`).join(", ");
|
|
2862
|
+
const noun = properties.length === 1 ? "property" : "properties";
|
|
2863
|
+
return {
|
|
2864
|
+
path,
|
|
2865
|
+
keyword: "additionalProperties",
|
|
2866
|
+
property: properties.join(","),
|
|
2867
|
+
message: `${this.formatPath(path)} has unsupported ${noun}: ${propertyList}.`
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
deduplicateSchemaIssues(issues) {
|
|
2871
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2872
|
+
const result = [];
|
|
2873
|
+
for (const issue of issues) {
|
|
2874
|
+
const key = `${issue.path}:${issue.keyword}:${issue.property ?? ""}:${issue.message}`;
|
|
2875
|
+
if (seen.has(key)) continue;
|
|
2876
|
+
seen.add(key);
|
|
2877
|
+
result.push(issue);
|
|
2878
|
+
}
|
|
2879
|
+
return result;
|
|
2880
|
+
}
|
|
2881
|
+
isRedundantOneOfIssue(issue, specificIssuePaths) {
|
|
2882
|
+
if (issue.keyword !== "oneOf") return false;
|
|
2883
|
+
return specificIssuePaths.some((path) => path === issue.path || path.startsWith(`${issue.path}/`));
|
|
2884
|
+
}
|
|
2885
|
+
formatPath(path) {
|
|
2886
|
+
if (!path || path === "/") return "Form definition";
|
|
2887
|
+
const segments = path.split("/").filter(Boolean);
|
|
2888
|
+
const parts = [];
|
|
2889
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
2890
|
+
const segment = segments[index];
|
|
2891
|
+
const nextSegment = segments[index + 1];
|
|
2892
|
+
if (segment === void 0) continue;
|
|
2893
|
+
if (segment === "content" && nextSegment !== void 0 && /^\d+$/.test(nextSegment)) {
|
|
2894
|
+
parts.push(`${parts.length === 0 ? "Content" : "content"} item ${Number(nextSegment) + 1}`);
|
|
2895
|
+
index += 1;
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
if (segment === "validation" && parts.length > 0) {
|
|
2899
|
+
parts[parts.length - 1] = `${parts[parts.length - 1]} validation`;
|
|
2900
|
+
continue;
|
|
2901
|
+
}
|
|
2902
|
+
parts.push(segment);
|
|
2903
|
+
}
|
|
2904
|
+
return parts.join(" > ");
|
|
2905
|
+
}
|
|
2906
|
+
getValueAtPath(input, path) {
|
|
2907
|
+
if (!path) return input;
|
|
2908
|
+
return path.split("/").filter(Boolean).reduce((value, segment) => {
|
|
2909
|
+
if (Array.isArray(value)) return value[Number(segment)];
|
|
2910
|
+
if (this.isRecord(value)) return value[segment];
|
|
2911
|
+
}, input);
|
|
2912
|
+
}
|
|
2913
|
+
getParentPath(path) {
|
|
2914
|
+
const segments = path.split("/").filter(Boolean);
|
|
2915
|
+
return segments.length > 1 ? `/${segments.slice(0, -1).join("/")}` : "/";
|
|
2916
|
+
}
|
|
2917
|
+
getLastPathSegment(path) {
|
|
2918
|
+
return path.split("/").filter(Boolean).at(-1);
|
|
2919
|
+
}
|
|
2920
|
+
isContentItemPath(path) {
|
|
2921
|
+
const segments = path.split("/").filter(Boolean);
|
|
2922
|
+
return segments.at(-2) === "content" && /^\d+$/.test(segments.at(-1) ?? "");
|
|
2923
|
+
}
|
|
2924
|
+
formatArticle(value) {
|
|
2925
|
+
if (!value) return "a";
|
|
2926
|
+
return /^[aeiou]/i.test(value) ? "an" : "a";
|
|
2927
|
+
}
|
|
2928
|
+
isRecord(value) {
|
|
2929
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1588
2930
|
}
|
|
1589
2931
|
/**
|
|
1590
2932
|
* Validates a form definition semantically.
|
|
@@ -1597,23 +2939,52 @@ var FormDefinitionValidator = class {
|
|
|
1597
2939
|
const issues = [];
|
|
1598
2940
|
this.checkDuplicateIds(definition.content, issues);
|
|
1599
2941
|
this.checkNestingDepth(definition.content, 0, issues);
|
|
1600
|
-
this.
|
|
1601
|
-
this.
|
|
2942
|
+
const arrayObjItemIds = this.collectArrayObjItemIds(definition.content);
|
|
2943
|
+
this.checkConditionRefsArrayObjItem(registry, arrayObjItemIds, issues);
|
|
2944
|
+
this.checkConditionRefs(registry, arrayObjItemIds, issues);
|
|
2945
|
+
this.checkConditionRefsValueless(registry, issues);
|
|
1602
2946
|
this.checkConstraintContradictions(registry, issues);
|
|
1603
2947
|
this.checkInvalidRegex(registry, issues);
|
|
1604
2948
|
return issues;
|
|
1605
2949
|
}
|
|
1606
2950
|
checkDuplicateIds(content, issues) {
|
|
1607
2951
|
const seen = /* @__PURE__ */ new Set();
|
|
1608
|
-
|
|
1609
|
-
if (seen.has(
|
|
2952
|
+
const record = (id) => {
|
|
2953
|
+
if (seen.has(id)) issues.push({
|
|
1610
2954
|
code: "DUPLICATE_ID",
|
|
1611
|
-
message: `Duplicate id: ${
|
|
1612
|
-
itemId:
|
|
2955
|
+
message: `Duplicate id: ${id}`,
|
|
2956
|
+
itemId: id
|
|
1613
2957
|
});
|
|
1614
|
-
else seen.add(
|
|
2958
|
+
else seen.add(id);
|
|
2959
|
+
};
|
|
2960
|
+
this.walkItems(content, (item) => {
|
|
2961
|
+
record(item.id);
|
|
2962
|
+
if (item.type === "array_obj") for (const subField of item.items ?? []) record(subField.id);
|
|
1615
2963
|
});
|
|
1616
2964
|
}
|
|
2965
|
+
collectArrayObjItemIds(content) {
|
|
2966
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2967
|
+
this.walkItems(content, (item) => {
|
|
2968
|
+
if (item.type !== "array_obj") return;
|
|
2969
|
+
for (const subField of item.items ?? []) if (!ids.has(subField.id)) ids.set(subField.id, item.id);
|
|
2970
|
+
});
|
|
2971
|
+
return ids;
|
|
2972
|
+
}
|
|
2973
|
+
checkConditionRefsArrayObjItem(registry, arrayObjItemIds, issues) {
|
|
2974
|
+
if (arrayObjItemIds.size === 0) return;
|
|
2975
|
+
for (const [id, entry] of registry) {
|
|
2976
|
+
if (!entry.condition) continue;
|
|
2977
|
+
for (const ref of DependencyGraph.extractFieldRefs(entry.condition)) {
|
|
2978
|
+
const containerId = arrayObjItemIds.get(ref);
|
|
2979
|
+
if (containerId === void 0) continue;
|
|
2980
|
+
issues.push({
|
|
2981
|
+
code: "CONDITION_REFS_ARRAY_OBJ_ITEM",
|
|
2982
|
+
message: `Condition references sub-field ${ref} of array_obj field ${containerId}, whose value is per-row and not addressable (in item ${id})`,
|
|
2983
|
+
itemId: id
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
1617
2988
|
checkNestingDepth(content, depth, issues) {
|
|
1618
2989
|
for (const item of content) if (item.type === "section") if (depth >= 3) issues.push({
|
|
1619
2990
|
code: "NESTING_DEPTH",
|
|
@@ -1622,28 +2993,37 @@ var FormDefinitionValidator = class {
|
|
|
1622
2993
|
});
|
|
1623
2994
|
else this.checkNestingDepth(item.content, depth + 1, issues);
|
|
1624
2995
|
}
|
|
1625
|
-
checkConditionRefs(registry, issues) {
|
|
2996
|
+
checkConditionRefs(registry, arrayObjItemIds, issues) {
|
|
1626
2997
|
for (const [id, entry] of registry) {
|
|
1627
2998
|
if (!entry.condition) continue;
|
|
1628
2999
|
const refs = DependencyGraph.extractFieldRefs(entry.condition);
|
|
1629
|
-
for (const ref of refs)
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
3000
|
+
for (const ref of refs) {
|
|
3001
|
+
if (arrayObjItemIds.has(ref)) continue;
|
|
3002
|
+
if (!registry.has(ref)) issues.push({
|
|
3003
|
+
code: "UNKNOWN_FIELD_REF",
|
|
3004
|
+
message: `Condition references unknown field: ${ref} (in item ${id})`,
|
|
3005
|
+
itemId: id
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
1634
3008
|
}
|
|
1635
3009
|
}
|
|
1636
|
-
|
|
3010
|
+
checkConditionRefsValueless(registry, issues) {
|
|
1637
3011
|
for (const [id, entry] of registry) {
|
|
1638
3012
|
if (!entry.condition) continue;
|
|
1639
3013
|
const refs = DependencyGraph.extractFieldRefs(entry.condition);
|
|
1640
3014
|
for (const ref of refs) {
|
|
1641
3015
|
const refEntry = registry.get(ref);
|
|
1642
|
-
if (refEntry
|
|
3016
|
+
if (!refEntry) continue;
|
|
3017
|
+
if (refEntry.type === "section") issues.push({
|
|
1643
3018
|
code: "CONDITION_REFS_SECTION",
|
|
1644
3019
|
message: `Condition references section ${ref}, which has no value (in item ${id})`,
|
|
1645
3020
|
itemId: id
|
|
1646
3021
|
});
|
|
3022
|
+
if (refEntry.type === "static") issues.push({
|
|
3023
|
+
code: "CONDITION_REFS_STATIC",
|
|
3024
|
+
message: `Condition references static field ${ref}, which has no value (in item ${id})`,
|
|
3025
|
+
itemId: id
|
|
3026
|
+
});
|
|
1647
3027
|
}
|
|
1648
3028
|
}
|
|
1649
3029
|
}
|
|
@@ -1684,7 +3064,8 @@ var FormDefinitionValidator = class {
|
|
|
1684
3064
|
}
|
|
1685
3065
|
break;
|
|
1686
3066
|
}
|
|
1687
|
-
case "array":
|
|
3067
|
+
case "array":
|
|
3068
|
+
case "array_obj": {
|
|
1688
3069
|
const v = entry.validation;
|
|
1689
3070
|
if (v.minItems !== void 0 && v.maxItems !== void 0 && v.maxItems < v.minItems) issues.push({
|
|
1690
3071
|
code: "INVALID_MIN_MAX",
|
|
@@ -1770,6 +3151,7 @@ var VisibilityResolver = class {
|
|
|
1770
3151
|
registry;
|
|
1771
3152
|
conditionEvaluator;
|
|
1772
3153
|
topologicalOrder;
|
|
3154
|
+
fieldTypes;
|
|
1773
3155
|
/**
|
|
1774
3156
|
* @param registry - The engine's field registry.
|
|
1775
3157
|
* @param conditionEvaluator - Evaluator for condition trees.
|
|
@@ -1779,6 +3161,7 @@ var VisibilityResolver = class {
|
|
|
1779
3161
|
this.registry = registry;
|
|
1780
3162
|
this.conditionEvaluator = conditionEvaluator;
|
|
1781
3163
|
this.topologicalOrder = topologicalOrder;
|
|
3164
|
+
this.fieldTypes = new Map([...registry].map(([id, entry]) => [id, entry.type]));
|
|
1782
3165
|
}
|
|
1783
3166
|
/**
|
|
1784
3167
|
* Determines whether a single field or section is visible.
|
|
@@ -1805,7 +3188,8 @@ var VisibilityResolver = class {
|
|
|
1805
3188
|
if (entry.condition) {
|
|
1806
3189
|
if (!this.conditionEvaluator.evalCondition(entry.condition, {
|
|
1807
3190
|
values,
|
|
1808
|
-
now
|
|
3191
|
+
now,
|
|
3192
|
+
fieldTypes: this.fieldTypes
|
|
1809
3193
|
})) return false;
|
|
1810
3194
|
}
|
|
1811
3195
|
if (entry.parentId !== void 0) return this.isVisible(entry.parentId, values, now);
|
|
@@ -1843,7 +3227,8 @@ var VisibilityResolver = class {
|
|
|
1843
3227
|
const visible = this.conditionEvaluator.evalCondition(entry.condition, {
|
|
1844
3228
|
values,
|
|
1845
3229
|
visibilityMap: result,
|
|
1846
|
-
now
|
|
3230
|
+
now,
|
|
3231
|
+
fieldTypes: this.fieldTypes
|
|
1847
3232
|
});
|
|
1848
3233
|
result.set(id, visible);
|
|
1849
3234
|
} else result.set(id, true);
|
|
@@ -2110,14 +3495,16 @@ var FormEngine = class FormEngine {
|
|
|
2110
3495
|
}
|
|
2111
3496
|
static walkContent(content, parentId, registry, contentOrder) {
|
|
2112
3497
|
for (const item of content) {
|
|
3498
|
+
const isValueField = item.type !== "section" && item.type !== "static";
|
|
2113
3499
|
const entry = {
|
|
2114
3500
|
id: item.id,
|
|
2115
3501
|
type: item.type,
|
|
2116
3502
|
condition: item.condition,
|
|
2117
|
-
validation:
|
|
3503
|
+
validation: isValueField ? item.validation : void 0,
|
|
2118
3504
|
parentId,
|
|
2119
|
-
options: item.type === "select" ? item.options : void 0,
|
|
3505
|
+
options: item.type === "select" || item.type === "multiselect" ? item.options : void 0,
|
|
2120
3506
|
item: item.type === "array" ? item.item : void 0,
|
|
3507
|
+
items: item.type === "array_obj" ? item.items : void 0,
|
|
2121
3508
|
label: item.type !== "section" ? item.label : void 0,
|
|
2122
3509
|
title: item.type === "section" ? item.title : void 0
|
|
2123
3510
|
};
|
|
@@ -2128,6 +3515,108 @@ var FormEngine = class FormEngine {
|
|
|
2128
3515
|
}
|
|
2129
3516
|
};
|
|
2130
3517
|
//#endregion
|
|
3518
|
+
//#region src/suggestions.ts
|
|
3519
|
+
/**
|
|
3520
|
+
* Pure helpers for reading and writing the `suggestions` block of a
|
|
3521
|
+
* {@link FormDocument}.
|
|
3522
|
+
*
|
|
3523
|
+
* Every function is total over legacy documents: `doc.suggestions` may be
|
|
3524
|
+
* `undefined` (documents written before suggestions existed) and each helper
|
|
3525
|
+
* treats that as "no suggestions". Writers never mutate their input -- they
|
|
3526
|
+
* return a new document -- and they drop the `suggestions` key entirely once it
|
|
3527
|
+
* would be empty, so a document that never had suggestions round-trips
|
|
3528
|
+
* unchanged.
|
|
3529
|
+
*
|
|
3530
|
+
* These helpers deliberately do **not** check that `fieldId` refers to an
|
|
3531
|
+
* existing, non-section field. That check belongs to the general-purpose
|
|
3532
|
+
* authoring API ({@link FormValuesEditor}); callers that already resolved a
|
|
3533
|
+
* field -- such as the viewer's render loop -- would only pay for it twice.
|
|
3534
|
+
*/
|
|
3535
|
+
/**
|
|
3536
|
+
* Returns the suggestion recorded for a field.
|
|
3537
|
+
*
|
|
3538
|
+
* @param doc - The form document to read from.
|
|
3539
|
+
* @param fieldId - Numeric id of the field.
|
|
3540
|
+
* @returns The suggestion, or `undefined` when the field has none.
|
|
3541
|
+
*/
|
|
3542
|
+
const getSuggestion = (doc, fieldId) => doc.suggestions?.[String(fieldId)];
|
|
3543
|
+
/**
|
|
3544
|
+
* Records a suggestion for a field, replacing any existing one.
|
|
3545
|
+
*
|
|
3546
|
+
* @param doc - The form document to update.
|
|
3547
|
+
* @param fieldId - Numeric id of the field.
|
|
3548
|
+
* @param suggestion - The suggestion to store. Deep-copied before storing.
|
|
3549
|
+
* @returns A new document carrying the suggestion.
|
|
3550
|
+
* @throws If `confidence` is not a finite number within `0..1`.
|
|
3551
|
+
*/
|
|
3552
|
+
const setSuggestion = (doc, fieldId, suggestion) => {
|
|
3553
|
+
const { confidence } = suggestion;
|
|
3554
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new Error(`Suggestion confidence must be a number between 0 and 1, received ${confidence}`);
|
|
3555
|
+
return {
|
|
3556
|
+
...doc,
|
|
3557
|
+
suggestions: {
|
|
3558
|
+
...doc.suggestions,
|
|
3559
|
+
[String(fieldId)]: deepCopy(suggestion)
|
|
3560
|
+
}
|
|
3561
|
+
};
|
|
3562
|
+
};
|
|
3563
|
+
/**
|
|
3564
|
+
* Removes the suggestion recorded for a field, leaving the field's value alone.
|
|
3565
|
+
*
|
|
3566
|
+
* When the removed entry was the last one, the `suggestions` key is dropped
|
|
3567
|
+
* from the document rather than left as an empty object.
|
|
3568
|
+
*
|
|
3569
|
+
* @param doc - The form document to update.
|
|
3570
|
+
* @param fieldId - Numeric id of the field.
|
|
3571
|
+
* @returns A new document without that suggestion, or `doc` itself when there
|
|
3572
|
+
* was nothing to remove.
|
|
3573
|
+
*/
|
|
3574
|
+
const removeSuggestion = (doc, fieldId) => {
|
|
3575
|
+
const key = String(fieldId);
|
|
3576
|
+
if (!doc.suggestions || !(key in doc.suggestions)) return doc;
|
|
3577
|
+
const { [key]: _removed, ...rest } = doc.suggestions;
|
|
3578
|
+
if (Object.keys(rest).length === 0) {
|
|
3579
|
+
const { suggestions: _dropped, ...withoutSuggestions } = doc;
|
|
3580
|
+
return withoutSuggestions;
|
|
3581
|
+
}
|
|
3582
|
+
return {
|
|
3583
|
+
...doc,
|
|
3584
|
+
suggestions: rest
|
|
3585
|
+
};
|
|
3586
|
+
};
|
|
3587
|
+
/**
|
|
3588
|
+
* Copies a field's suggested value into the document's values, keeping the
|
|
3589
|
+
* suggestion in place so the origin of the answer stays visible.
|
|
3590
|
+
*
|
|
3591
|
+
* The value is deep-copied: array and object suggestions must not become
|
|
3592
|
+
* aliases of the stored value, or later in-place edits (e.g. appending an array
|
|
3593
|
+
* item) would silently rewrite the suggestion too.
|
|
3594
|
+
*
|
|
3595
|
+
* @param doc - The form document to update.
|
|
3596
|
+
* @param fieldId - Numeric id of the field.
|
|
3597
|
+
* @returns A new document with the value applied, or `doc` itself when the
|
|
3598
|
+
* field has no suggestion.
|
|
3599
|
+
*/
|
|
3600
|
+
const applySuggestion = (doc, fieldId) => {
|
|
3601
|
+
const suggestion = getSuggestion(doc, fieldId);
|
|
3602
|
+
if (!suggestion) return doc;
|
|
3603
|
+
return {
|
|
3604
|
+
...doc,
|
|
3605
|
+
values: {
|
|
3606
|
+
...doc.values,
|
|
3607
|
+
[String(fieldId)]: deepCopy(suggestion.value)
|
|
3608
|
+
}
|
|
3609
|
+
};
|
|
3610
|
+
};
|
|
3611
|
+
/**
|
|
3612
|
+
* Returns all suggestions on a document as a plain object, never `undefined`.
|
|
3613
|
+
*
|
|
3614
|
+
* @param doc - The form document to read from.
|
|
3615
|
+
* @returns A copy of the suggestions map. Empty when the document has none.
|
|
3616
|
+
*/
|
|
3617
|
+
const getSuggestions = (doc) => ({ ...doc.suggestions });
|
|
3618
|
+
const deepCopy = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
3619
|
+
//#endregion
|
|
2131
3620
|
//#region src/form-values-editor.ts
|
|
2132
3621
|
/**
|
|
2133
3622
|
* Mutable editor for building and modifying form values against a {@link FormDefinition}.
|
|
@@ -2199,13 +3688,16 @@ var FormValuesEditor = class {
|
|
|
2199
3688
|
* If the field currently has no value, it is initialized to an empty array
|
|
2200
3689
|
* before appending.
|
|
2201
3690
|
*
|
|
2202
|
-
* @param fieldId - Numeric id of the array field.
|
|
2203
|
-
* @param value - The value to append. Defaults to `undefined
|
|
3691
|
+
* @param fieldId - Numeric id of the `array` or `array_obj` field.
|
|
3692
|
+
* @param value - The value to append. Defaults to `undefined` for an
|
|
3693
|
+
* `array` field and to an empty row (`{}`) for an `array_obj` field.
|
|
2204
3694
|
* @returns `this` for chaining.
|
|
2205
3695
|
* @throws If `fieldId` is not an array field.
|
|
2206
3696
|
*/
|
|
2207
3697
|
addArrayItem(fieldId, value) {
|
|
2208
|
-
this.getOrInitArray(fieldId)
|
|
3698
|
+
const arr = this.getOrInitArray(fieldId);
|
|
3699
|
+
const entry = this.engine.getFieldDef(fieldId);
|
|
3700
|
+
arr.push(value === void 0 && entry?.type === "array_obj" ? {} : value);
|
|
2209
3701
|
return this;
|
|
2210
3702
|
}
|
|
2211
3703
|
/**
|
|
@@ -2255,6 +3747,114 @@ var FormValuesEditor = class {
|
|
|
2255
3747
|
return this;
|
|
2256
3748
|
}
|
|
2257
3749
|
/**
|
|
3750
|
+
* Returns the value of one sub-field within one row of an `array_obj` field.
|
|
3751
|
+
*
|
|
3752
|
+
* @param fieldId - Numeric id of the `array_obj` field.
|
|
3753
|
+
* @param index - Zero-based row index.
|
|
3754
|
+
* @param subFieldId - Numeric id of the sub-field.
|
|
3755
|
+
* @returns The sub-field value, or `undefined` if not set.
|
|
3756
|
+
* @throws If the field is not an `array_obj`, the row index is out of
|
|
3757
|
+
* bounds, or the sub-field does not belong to the field.
|
|
3758
|
+
*/
|
|
3759
|
+
getArrayObjValue(fieldId, index, subFieldId) {
|
|
3760
|
+
return this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)];
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Sets the value of one sub-field within one row of an `array_obj` field.
|
|
3764
|
+
*
|
|
3765
|
+
* @param fieldId - Numeric id of the `array_obj` field.
|
|
3766
|
+
* @param index - Zero-based row index.
|
|
3767
|
+
* @param subFieldId - Numeric id of the sub-field.
|
|
3768
|
+
* @param value - The value to set.
|
|
3769
|
+
* @returns `this` for chaining.
|
|
3770
|
+
* @throws If the field is not an `array_obj`, the row index is out of
|
|
3771
|
+
* bounds, or the sub-field does not belong to the field or is static.
|
|
3772
|
+
*/
|
|
3773
|
+
setArrayObjValue(fieldId, index, subFieldId, value) {
|
|
3774
|
+
this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)] = value;
|
|
3775
|
+
return this;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Removes one sub-field's value from one row of an `array_obj` field.
|
|
3779
|
+
*
|
|
3780
|
+
* @returns `this` for chaining.
|
|
3781
|
+
* @throws Under the same conditions as {@link setArrayObjValue}.
|
|
3782
|
+
*/
|
|
3783
|
+
clearArrayObjValue(fieldId, index, subFieldId) {
|
|
3784
|
+
delete this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)];
|
|
3785
|
+
return this;
|
|
3786
|
+
}
|
|
3787
|
+
/**
|
|
3788
|
+
* Returns the suggestion recorded for a field.
|
|
3789
|
+
*
|
|
3790
|
+
* @param fieldId - Numeric id of the field.
|
|
3791
|
+
* @returns The suggestion, or `undefined` if the field has none.
|
|
3792
|
+
*/
|
|
3793
|
+
getSuggestion(fieldId) {
|
|
3794
|
+
return getSuggestion(this.doc, fieldId);
|
|
3795
|
+
}
|
|
3796
|
+
/**
|
|
3797
|
+
* Returns every suggestion on the document, keyed by stringified field id.
|
|
3798
|
+
*
|
|
3799
|
+
* @returns A copy of the suggestions map. Empty when there are none.
|
|
3800
|
+
*/
|
|
3801
|
+
getSuggestions() {
|
|
3802
|
+
return getSuggestions(this.doc);
|
|
3803
|
+
}
|
|
3804
|
+
/**
|
|
3805
|
+
* Records a suggestion for a field, replacing any existing one. The field's
|
|
3806
|
+
* value is left untouched.
|
|
3807
|
+
*
|
|
3808
|
+
* @param fieldId - Numeric id of the field.
|
|
3809
|
+
* @param suggestion - The suggestion to store.
|
|
3810
|
+
* @returns `this` for chaining.
|
|
3811
|
+
* @throws If `fieldId` is unknown, references a section, or `confidence`
|
|
3812
|
+
* falls outside `0..1`.
|
|
3813
|
+
*/
|
|
3814
|
+
setSuggestion(fieldId, suggestion) {
|
|
3815
|
+
this.assertField(fieldId);
|
|
3816
|
+
this.doc = setSuggestion(this.doc, fieldId, suggestion);
|
|
3817
|
+
return this;
|
|
3818
|
+
}
|
|
3819
|
+
/**
|
|
3820
|
+
* Removes the suggestion recorded for a field, leaving its value untouched.
|
|
3821
|
+
*
|
|
3822
|
+
* @param fieldId - Numeric id of the field.
|
|
3823
|
+
* @returns `this` for chaining.
|
|
3824
|
+
*/
|
|
3825
|
+
clearSuggestion(fieldId) {
|
|
3826
|
+
this.doc = removeSuggestion(this.doc, fieldId);
|
|
3827
|
+
return this;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Accepts a field's suggestion: copies the suggested value into the field's
|
|
3831
|
+
* value and **keeps** the suggestion, so its source stays visible.
|
|
3832
|
+
*
|
|
3833
|
+
* No-op when the field has no suggestion.
|
|
3834
|
+
*
|
|
3835
|
+
* @param fieldId - Numeric id of the field.
|
|
3836
|
+
* @returns `this` for chaining.
|
|
3837
|
+
* @throws If `fieldId` is unknown or references a section.
|
|
3838
|
+
*/
|
|
3839
|
+
acceptSuggestion(fieldId) {
|
|
3840
|
+
this.assertField(fieldId);
|
|
3841
|
+
this.doc = applySuggestion(this.doc, fieldId);
|
|
3842
|
+
return this;
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3845
|
+
* Rejects a field's suggestion: removes it entirely, leaving the field's
|
|
3846
|
+
* value untouched.
|
|
3847
|
+
*
|
|
3848
|
+
* No-op when the field has no suggestion.
|
|
3849
|
+
*
|
|
3850
|
+
* @param fieldId - Numeric id of the field.
|
|
3851
|
+
* @returns `this` for chaining.
|
|
3852
|
+
*/
|
|
3853
|
+
rejectSuggestion(fieldId) {
|
|
3854
|
+
this.doc = removeSuggestion(this.doc, fieldId);
|
|
3855
|
+
return this;
|
|
3856
|
+
}
|
|
3857
|
+
/**
|
|
2258
3858
|
* Sets the `submittedAt` timestamp on the document.
|
|
2259
3859
|
*
|
|
2260
3860
|
* @param submittedAt - ISO 8601 timestamp string.
|
|
@@ -2310,6 +3910,7 @@ var FormValuesEditor = class {
|
|
|
2310
3910
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2311
3911
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2312
3912
|
if (entry.type === "section") throw new Error(`Item ${fieldId} is a section, not a field`);
|
|
3913
|
+
if (entry.type === "static") throw new Error(`Item ${fieldId} is a static field and holds no value`);
|
|
2313
3914
|
}
|
|
2314
3915
|
/**
|
|
2315
3916
|
* Asserts that `fieldId` is an array field and returns the current array value.
|
|
@@ -2318,7 +3919,7 @@ var FormValuesEditor = class {
|
|
|
2318
3919
|
assertArray(fieldId) {
|
|
2319
3920
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2320
3921
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2321
|
-
if (entry.type !== "array") throw new Error(`Field ${fieldId} is not an array field`);
|
|
3922
|
+
if (entry.type !== "array" && entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array field`);
|
|
2322
3923
|
const val = this.doc.values[String(fieldId)];
|
|
2323
3924
|
if (!Array.isArray(val)) throw new Error(`Field ${fieldId} does not currently hold an array value`);
|
|
2324
3925
|
return val;
|
|
@@ -2329,7 +3930,7 @@ var FormValuesEditor = class {
|
|
|
2329
3930
|
getOrInitArray(fieldId) {
|
|
2330
3931
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2331
3932
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2332
|
-
if (entry.type !== "array") throw new Error(`Field ${fieldId} is not an array field`);
|
|
3933
|
+
if (entry.type !== "array" && entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array field`);
|
|
2333
3934
|
const key = String(fieldId);
|
|
2334
3935
|
let val = this.doc.values[key];
|
|
2335
3936
|
if (!Array.isArray(val)) {
|
|
@@ -2338,6 +3939,26 @@ var FormValuesEditor = class {
|
|
|
2338
3939
|
}
|
|
2339
3940
|
return val;
|
|
2340
3941
|
}
|
|
3942
|
+
/**
|
|
3943
|
+
* Asserts that `fieldId` is an `array_obj` field holding a row at `index`
|
|
3944
|
+
* with a writable sub-field `subFieldId`, and returns that row.
|
|
3945
|
+
* The row is created in place when it is absent or not an object.
|
|
3946
|
+
*/
|
|
3947
|
+
assertArrayObjRow(fieldId, index, subFieldId) {
|
|
3948
|
+
const entry = this.engine.getFieldDef(fieldId);
|
|
3949
|
+
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
3950
|
+
if (entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array_obj field`);
|
|
3951
|
+
const subField = (entry.items ?? []).find((f) => f.id === subFieldId);
|
|
3952
|
+
if (!subField) throw new Error(`Sub-field ${subFieldId} does not belong to field ${fieldId}`);
|
|
3953
|
+
if (subField.type === "static") throw new Error(`Sub-field ${subFieldId} is a static field and holds no value`);
|
|
3954
|
+
const arr = this.assertArray(fieldId);
|
|
3955
|
+
if (index < 0 || index >= arr.length) throw new Error(`Index ${index} is out of bounds for array field ${fieldId} (length ${arr.length})`);
|
|
3956
|
+
const row = arr[index];
|
|
3957
|
+
if (typeof row === "object" && row !== null && !Array.isArray(row)) return row;
|
|
3958
|
+
const created = {};
|
|
3959
|
+
arr[index] = created;
|
|
3960
|
+
return created;
|
|
3961
|
+
}
|
|
2341
3962
|
};
|
|
2342
3963
|
//#endregion
|
|
2343
3964
|
exports.ConditionEvaluator = ConditionEvaluator;
|
|
@@ -2349,3 +3970,8 @@ exports.FormDefinitionValidator = FormDefinitionValidator;
|
|
|
2349
3970
|
exports.FormEngine = FormEngine;
|
|
2350
3971
|
exports.FormValuesEditor = FormValuesEditor;
|
|
2351
3972
|
exports.VisibilityResolver = VisibilityResolver;
|
|
3973
|
+
exports.applySuggestion = applySuggestion;
|
|
3974
|
+
exports.getSuggestion = getSuggestion;
|
|
3975
|
+
exports.getSuggestions = getSuggestions;
|
|
3976
|
+
exports.removeSuggestion = removeSuggestion;
|
|
3977
|
+
exports.setSuggestion = setSuggestion;
|