@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.mjs
CHANGED
|
@@ -86,6 +86,18 @@ const resolveRelativeDate = (relative, now) => {
|
|
|
86
86
|
*
|
|
87
87
|
* **Date handling**: condition values that are relative date expressions
|
|
88
88
|
* (e.g. `"+7d"`) are resolved against `ctx.now` before comparison.
|
|
89
|
+
*
|
|
90
|
+
* **Multiselect handling**: when `ctx.fieldTypes` reports the referenced field
|
|
91
|
+
* as `multiselect`, its array value is compared as a set instead of a scalar:
|
|
92
|
+
* `set` means "at least one option chosen", `eq`/`ne` compare set membership
|
|
93
|
+
* ignoring order and duplicates, `in`/`notin` test whether *any* chosen option
|
|
94
|
+
* appears in the condition's list, and the ordering operators are always
|
|
95
|
+
* `false`. Fields of every other type -- including `array` -- keep the original
|
|
96
|
+
* scalar semantics.
|
|
97
|
+
*
|
|
98
|
+
* **Object-valued fields** (`file`, `sustainability`) go through the scalar path
|
|
99
|
+
* too, so only `set`/`notset` are meaningful: any object present counts as
|
|
100
|
+
* `set`, and `eq`/`ne`/`in`/`notin` compare by reference and so never match.
|
|
89
101
|
*/
|
|
90
102
|
var ConditionEvaluator = class {
|
|
91
103
|
/**
|
|
@@ -104,6 +116,7 @@ var ConditionEvaluator = class {
|
|
|
104
116
|
evalSimple(cond, ctx) {
|
|
105
117
|
if (ctx.visibilityMap && ctx.visibilityMap.get(cond.field) === false) return cond.op === "notset";
|
|
106
118
|
const fieldValue = ctx.values[String(cond.field)];
|
|
119
|
+
if (ctx.fieldTypes?.get(cond.field) === "multiselect") return this.evalMultiselect(cond, fieldValue);
|
|
107
120
|
switch (cond.op) {
|
|
108
121
|
case "set": return fieldValue !== null && fieldValue !== void 0 && fieldValue !== "";
|
|
109
122
|
case "notset": return fieldValue === null || fieldValue === void 0 || fieldValue === "";
|
|
@@ -118,6 +131,32 @@ var ConditionEvaluator = class {
|
|
|
118
131
|
default: return false;
|
|
119
132
|
}
|
|
120
133
|
}
|
|
134
|
+
evalMultiselect(cond, fieldValue) {
|
|
135
|
+
const selected = Array.isArray(fieldValue) ? fieldValue : [];
|
|
136
|
+
const candidates = cond.value;
|
|
137
|
+
switch (cond.op) {
|
|
138
|
+
case "set": return selected.length > 0;
|
|
139
|
+
case "notset": return selected.length === 0;
|
|
140
|
+
case "eq": return this.sameSet(selected, candidates);
|
|
141
|
+
case "ne": return !this.sameSet(selected, candidates);
|
|
142
|
+
case "in": return Array.isArray(candidates) && selected.some((item) => candidates.includes(item));
|
|
143
|
+
case "notin": return Array.isArray(candidates) && !selected.some((item) => candidates.includes(item));
|
|
144
|
+
default: return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compares the chosen options against a condition value as sets, ignoring
|
|
149
|
+
* order and duplicates. A scalar condition value is read as a one-element
|
|
150
|
+
* set, so `{ op: 'eq', value: 'a' }` means "`a` is the only option chosen".
|
|
151
|
+
*/
|
|
152
|
+
sameSet(selected, expected) {
|
|
153
|
+
const expectedItems = Array.isArray(expected) ? expected : [expected];
|
|
154
|
+
const selectedSet = new Set(selected);
|
|
155
|
+
const expectedSet = new Set(expectedItems);
|
|
156
|
+
if (selectedSet.size !== expectedSet.size) return false;
|
|
157
|
+
for (const item of expectedSet) if (!selectedSet.has(item)) return false;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
121
160
|
resolveIfDate(value, now) {
|
|
122
161
|
if (isRelativeDate(value)) return resolveRelativeDate(value, now);
|
|
123
162
|
return value;
|
|
@@ -378,6 +417,93 @@ var DependencyGraph = class DependencyGraph {
|
|
|
378
417
|
}
|
|
379
418
|
};
|
|
380
419
|
//#endregion
|
|
420
|
+
//#region src/validators/array-obj-validator.ts
|
|
421
|
+
const isRow = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
422
|
+
/**
|
|
423
|
+
* Validates an `array_obj` field: the row list itself, then every sub-field of
|
|
424
|
+
* every row.
|
|
425
|
+
*
|
|
426
|
+
* Sub-field errors keep the container's `fieldId` -- matching the key the
|
|
427
|
+
* `fieldErrors` map stores them under -- and carry `itemIndex` (the row) plus
|
|
428
|
+
* `itemFieldId` (the sub-field). A row-level `TYPE` error carries `itemIndex`
|
|
429
|
+
* only.
|
|
430
|
+
*/
|
|
431
|
+
var ArrayObjValidator = class {
|
|
432
|
+
validate(ctx) {
|
|
433
|
+
const { fieldId, value, now } = ctx;
|
|
434
|
+
const { items, validateField } = ctx;
|
|
435
|
+
const validation = ctx.validation;
|
|
436
|
+
const errors = [];
|
|
437
|
+
if (value === null || value === void 0) return errors;
|
|
438
|
+
if (!Array.isArray(value)) {
|
|
439
|
+
errors.push({
|
|
440
|
+
fieldId,
|
|
441
|
+
rule: "TYPE",
|
|
442
|
+
message: "Must be an array",
|
|
443
|
+
params: { expectedType: "array" }
|
|
444
|
+
});
|
|
445
|
+
return errors;
|
|
446
|
+
}
|
|
447
|
+
if (validation?.minItems !== void 0 && value.length < validation.minItems) errors.push({
|
|
448
|
+
fieldId,
|
|
449
|
+
rule: "MIN_ITEMS",
|
|
450
|
+
message: `Must have at least ${validation.minItems} items`,
|
|
451
|
+
params: {
|
|
452
|
+
minItems: validation.minItems,
|
|
453
|
+
actual: value.length
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
if (validation?.maxItems !== void 0 && value.length > validation.maxItems) errors.push({
|
|
457
|
+
fieldId,
|
|
458
|
+
rule: "MAX_ITEMS",
|
|
459
|
+
message: `Must have at most ${validation.maxItems} items`,
|
|
460
|
+
params: {
|
|
461
|
+
maxItems: validation.maxItems,
|
|
462
|
+
actual: value.length
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
if (!items) return errors;
|
|
466
|
+
for (let i = 0; i < value.length; i++) {
|
|
467
|
+
const raw = value[i];
|
|
468
|
+
if (raw !== null && raw !== void 0 && !isRow(raw)) {
|
|
469
|
+
errors.push({
|
|
470
|
+
fieldId,
|
|
471
|
+
rule: "TYPE",
|
|
472
|
+
message: "Must be an object",
|
|
473
|
+
params: { expectedType: "object" },
|
|
474
|
+
itemIndex: i
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const row = isRow(raw) ? raw : {};
|
|
479
|
+
for (const subField of items) {
|
|
480
|
+
if (subField.type === "static") continue;
|
|
481
|
+
const fakeEntry = {
|
|
482
|
+
id: subField.id,
|
|
483
|
+
type: subField.type,
|
|
484
|
+
condition: void 0,
|
|
485
|
+
validation: subField.validation,
|
|
486
|
+
parentId: fieldId,
|
|
487
|
+
options: subField.options,
|
|
488
|
+
item: void 0,
|
|
489
|
+
items: void 0,
|
|
490
|
+
label: subField.label,
|
|
491
|
+
title: void 0
|
|
492
|
+
};
|
|
493
|
+
const subErrors = validateField(subField.id, row[String(subField.id)], fakeEntry, now);
|
|
494
|
+
errors.push(...subErrors.map((err) => ({
|
|
495
|
+
...err,
|
|
496
|
+
fieldId,
|
|
497
|
+
itemIndex: i,
|
|
498
|
+
itemFieldId: subField.id,
|
|
499
|
+
message: `${subField.label}: ${err.message}`
|
|
500
|
+
})));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return errors;
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
//#endregion
|
|
381
507
|
//#region src/validators/array-validator.ts
|
|
382
508
|
var ArrayValidator = class {
|
|
383
509
|
validate(ctx) {
|
|
@@ -413,7 +539,7 @@ var ArrayValidator = class {
|
|
|
413
539
|
actual: value.length
|
|
414
540
|
}
|
|
415
541
|
});
|
|
416
|
-
if (item) for (let i = 0; i < value.length; i++) {
|
|
542
|
+
if (item && item.type !== "static") for (let i = 0; i < value.length; i++) {
|
|
417
543
|
const fakeEntry = {
|
|
418
544
|
id: fieldId,
|
|
419
545
|
type: item.type,
|
|
@@ -422,6 +548,7 @@ var ArrayValidator = class {
|
|
|
422
548
|
parentId: void 0,
|
|
423
549
|
options: item.options,
|
|
424
550
|
item: void 0,
|
|
551
|
+
items: void 0,
|
|
425
552
|
label: item.label,
|
|
426
553
|
title: void 0
|
|
427
554
|
};
|
|
@@ -435,6 +562,32 @@ var ArrayValidator = class {
|
|
|
435
562
|
}
|
|
436
563
|
};
|
|
437
564
|
//#endregion
|
|
565
|
+
//#region src/validators/blockchain-validator.ts
|
|
566
|
+
var BlockchainValidator = class {
|
|
567
|
+
validate(ctx) {
|
|
568
|
+
const { fieldId, value } = ctx;
|
|
569
|
+
const validation = ctx.validation;
|
|
570
|
+
const errors = [];
|
|
571
|
+
const isEmpty = value === null || value === void 0 || value === "";
|
|
572
|
+
if (validation?.required && isEmpty) {
|
|
573
|
+
errors.push({
|
|
574
|
+
fieldId,
|
|
575
|
+
rule: "REQUIRED",
|
|
576
|
+
message: "Value is required"
|
|
577
|
+
});
|
|
578
|
+
return errors;
|
|
579
|
+
}
|
|
580
|
+
if (isEmpty) return errors;
|
|
581
|
+
if (typeof value !== "string") errors.push({
|
|
582
|
+
fieldId,
|
|
583
|
+
rule: "TYPE",
|
|
584
|
+
message: "Must be a blockchain address string",
|
|
585
|
+
params: { expectedType: "blockchain" }
|
|
586
|
+
});
|
|
587
|
+
return errors;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
//#endregion
|
|
438
591
|
//#region src/validators/boolean-validator.ts
|
|
439
592
|
var BooleanValidator = class {
|
|
440
593
|
validate(ctx) {
|
|
@@ -532,6 +685,51 @@ var FileValidator = class {
|
|
|
532
685
|
}
|
|
533
686
|
};
|
|
534
687
|
//#endregion
|
|
688
|
+
//#region src/validators/multiselect-validator.ts
|
|
689
|
+
/**
|
|
690
|
+
* Validates `multiselect` fields, whose value is an array of the chosen
|
|
691
|
+
* options' `value`s.
|
|
692
|
+
*
|
|
693
|
+
* An absent value and an empty array both count as "nothing selected", so both
|
|
694
|
+
* fail `required`. Option membership is reported as a single field-level
|
|
695
|
+
* `INVALID_OPTION` error rather than one error per offending element: the
|
|
696
|
+
* viewer filters out errors carrying an `itemIndex` on the non-array render
|
|
697
|
+
* path, so indexed errors would never be displayed.
|
|
698
|
+
*/
|
|
699
|
+
var MultiselectValidator = class {
|
|
700
|
+
validate(ctx) {
|
|
701
|
+
const { fieldId, value } = ctx;
|
|
702
|
+
const validation = ctx.validation;
|
|
703
|
+
const options = ctx.options;
|
|
704
|
+
const errors = [];
|
|
705
|
+
const isEmpty = value === null || value === void 0 || Array.isArray(value) && value.length === 0;
|
|
706
|
+
if (validation?.required && isEmpty) {
|
|
707
|
+
errors.push({
|
|
708
|
+
fieldId,
|
|
709
|
+
rule: "REQUIRED",
|
|
710
|
+
message: "Value is required"
|
|
711
|
+
});
|
|
712
|
+
return errors;
|
|
713
|
+
}
|
|
714
|
+
if (isEmpty) return errors;
|
|
715
|
+
if (!Array.isArray(value)) {
|
|
716
|
+
errors.push({
|
|
717
|
+
fieldId,
|
|
718
|
+
rule: "TYPE",
|
|
719
|
+
message: "Must be an array",
|
|
720
|
+
params: { expectedType: "multiselect" }
|
|
721
|
+
});
|
|
722
|
+
return errors;
|
|
723
|
+
}
|
|
724
|
+
if (options && value.some((selected) => !options.some((opt) => opt.value === selected))) errors.push({
|
|
725
|
+
fieldId,
|
|
726
|
+
rule: "INVALID_OPTION",
|
|
727
|
+
message: "Value is not a valid option"
|
|
728
|
+
});
|
|
729
|
+
return errors;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
//#endregion
|
|
535
733
|
//#region src/validators/number-validator.ts
|
|
536
734
|
var NumberValidator = class {
|
|
537
735
|
validate(ctx) {
|
|
@@ -659,6 +857,53 @@ var StringValidator = class {
|
|
|
659
857
|
}
|
|
660
858
|
};
|
|
661
859
|
//#endregion
|
|
860
|
+
//#region src/validators/sustainability-validator.ts
|
|
861
|
+
const OPTIONAL_TEXT_PROPERTIES = ["ccri", "cmc"];
|
|
862
|
+
var SustainabilityValidator = class {
|
|
863
|
+
validate(ctx) {
|
|
864
|
+
const { fieldId, value } = ctx;
|
|
865
|
+
const validation = ctx.validation;
|
|
866
|
+
const errors = [];
|
|
867
|
+
if (value === null || value === void 0) {
|
|
868
|
+
if (validation?.required) errors.push({
|
|
869
|
+
fieldId,
|
|
870
|
+
rule: "REQUIRED",
|
|
871
|
+
message: "Value is required"
|
|
872
|
+
});
|
|
873
|
+
return errors;
|
|
874
|
+
}
|
|
875
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
876
|
+
errors.push({
|
|
877
|
+
fieldId,
|
|
878
|
+
rule: "TYPE",
|
|
879
|
+
message: "Must be a valid sustainability object",
|
|
880
|
+
params: { expectedType: "sustainability" }
|
|
881
|
+
});
|
|
882
|
+
return errors;
|
|
883
|
+
}
|
|
884
|
+
const record = value;
|
|
885
|
+
const address = record.address ?? "";
|
|
886
|
+
if (typeof address !== "string" || OPTIONAL_TEXT_PROPERTIES.some((key) => !isOptionalText(record[key]))) {
|
|
887
|
+
errors.push({
|
|
888
|
+
fieldId,
|
|
889
|
+
rule: "TYPE",
|
|
890
|
+
message: "Must be a valid sustainability object",
|
|
891
|
+
params: { expectedType: "sustainability" }
|
|
892
|
+
});
|
|
893
|
+
return errors;
|
|
894
|
+
}
|
|
895
|
+
if (validation?.required && address === "") errors.push({
|
|
896
|
+
fieldId,
|
|
897
|
+
rule: "REQUIRED",
|
|
898
|
+
message: "Value is required"
|
|
899
|
+
});
|
|
900
|
+
return errors;
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
function isOptionalText(value) {
|
|
904
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
905
|
+
}
|
|
906
|
+
//#endregion
|
|
662
907
|
//#region src/field-validator.ts
|
|
663
908
|
/**
|
|
664
909
|
* Validates form values against the schema's validation rules.
|
|
@@ -675,9 +920,19 @@ var StringValidator = class {
|
|
|
675
920
|
* - `date` -- `required`, `minDate`, `maxDate`. Relative date boundaries
|
|
676
921
|
* are resolved against `now`.
|
|
677
922
|
* - `select` -- `required`, plus the value must be one of the defined options.
|
|
923
|
+
* - `multiselect` -- `required` (an absent value and an empty array both fail),
|
|
924
|
+
* plus every selected value must be one of the defined options.
|
|
925
|
+
* - `static` -- never validated; static blocks hold no value.
|
|
926
|
+
* - `blockchain` -- `required` only. The CAIP address format is never checked.
|
|
927
|
+
* - `sustainability` -- `required` (the value's `address` must be a non-empty
|
|
928
|
+
* string) plus a shape check; `ccri` and `cmc` are optional text.
|
|
678
929
|
* - `array` -- `minItems`, `maxItems`, plus each item is validated
|
|
679
930
|
* individually according to the array's {@link ArrayItemDef}. Item-level
|
|
680
931
|
* errors carry an `itemIndex`.
|
|
932
|
+
* - `array_obj` -- `minItems`, `maxItems`, plus every sub-field of every row is
|
|
933
|
+
* validated according to the field's {@link ArrayObjItemDef} list. Sub-field
|
|
934
|
+
* errors carry an `itemIndex` (the row) and an `itemFieldId` (the sub-field),
|
|
935
|
+
* while `fieldId` stays the container's id.
|
|
681
936
|
*
|
|
682
937
|
* For all types, if `required` fails, no further rules are checked for that
|
|
683
938
|
* field (early return). If the value is empty/absent and `required` is not
|
|
@@ -697,8 +952,12 @@ var FieldValidator = class {
|
|
|
697
952
|
boolean: new BooleanValidator(),
|
|
698
953
|
date: new DateValidator(),
|
|
699
954
|
select: new SelectValidator(),
|
|
955
|
+
multiselect: new MultiselectValidator(),
|
|
700
956
|
array: new ArrayValidator(),
|
|
701
|
-
|
|
957
|
+
array_obj: new ArrayObjValidator(),
|
|
958
|
+
file: new FileValidator(),
|
|
959
|
+
blockchain: new BlockchainValidator(),
|
|
960
|
+
sustainability: new SustainabilityValidator()
|
|
702
961
|
};
|
|
703
962
|
}
|
|
704
963
|
/**
|
|
@@ -739,6 +998,17 @@ var FieldValidator = class {
|
|
|
739
998
|
};
|
|
740
999
|
return validator.validate(ctx);
|
|
741
1000
|
}
|
|
1001
|
+
if (entry.type === "array_obj") {
|
|
1002
|
+
const ctx = {
|
|
1003
|
+
fieldId,
|
|
1004
|
+
value,
|
|
1005
|
+
validation: entry.validation,
|
|
1006
|
+
now,
|
|
1007
|
+
items: entry.items,
|
|
1008
|
+
validateField: this.validateField.bind(this)
|
|
1009
|
+
};
|
|
1010
|
+
return validator.validate(ctx);
|
|
1011
|
+
}
|
|
742
1012
|
return validator.validate({
|
|
743
1013
|
fieldId,
|
|
744
1014
|
value,
|
|
@@ -793,11 +1063,17 @@ var FormDefinitionEditor = class {
|
|
|
793
1063
|
}
|
|
794
1064
|
/**
|
|
795
1065
|
* Returns the next available numeric id (max existing + 1).
|
|
1066
|
+
*
|
|
1067
|
+
* `array_obj` sub-field ids share the form-wide id space, so they count
|
|
1068
|
+
* here too -- otherwise a new top-level field could collide with one.
|
|
796
1069
|
*/
|
|
797
1070
|
nextId() {
|
|
798
1071
|
let max = 0;
|
|
799
1072
|
this.walkAll(this.definition.content, (item) => {
|
|
800
1073
|
if (item.id > max) max = item.id;
|
|
1074
|
+
if (item.type === "array_obj") {
|
|
1075
|
+
for (const subField of item.items ?? []) if (subField.id > max) max = subField.id;
|
|
1076
|
+
}
|
|
801
1077
|
});
|
|
802
1078
|
return max + 1;
|
|
803
1079
|
}
|
|
@@ -936,6 +1212,7 @@ var FormDefinitionEditor = class {
|
|
|
936
1212
|
const item = this.findItem(id);
|
|
937
1213
|
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
938
1214
|
if (item.type === "section") throw new Error("Sections do not have validation");
|
|
1215
|
+
if (item.type === "static") throw new Error("Static fields do not have validation");
|
|
939
1216
|
const field = item;
|
|
940
1217
|
if (validation === void 0) delete field.validation;
|
|
941
1218
|
else field.validation = validation;
|
|
@@ -952,12 +1229,12 @@ var FormDefinitionEditor = class {
|
|
|
952
1229
|
return this;
|
|
953
1230
|
}
|
|
954
1231
|
/**
|
|
955
|
-
* Sets the
|
|
1232
|
+
* Sets the options for a `select` or `multiselect` field.
|
|
956
1233
|
*/
|
|
957
1234
|
setOptions(id, options) {
|
|
958
1235
|
const item = this.findItem(id);
|
|
959
1236
|
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
960
|
-
if (item.type !== "select") throw new Error(`Field ${id} is not a select field`);
|
|
1237
|
+
if (item.type !== "select" && item.type !== "multiselect") throw new Error(`Field ${id} is not a select or multiselect field`);
|
|
961
1238
|
const field = item;
|
|
962
1239
|
field.options = options;
|
|
963
1240
|
return this;
|
|
@@ -974,6 +1251,94 @@ var FormDefinitionEditor = class {
|
|
|
974
1251
|
return this;
|
|
975
1252
|
}
|
|
976
1253
|
/**
|
|
1254
|
+
* Replaces the whole sub-field list of an `array_obj` field.
|
|
1255
|
+
*
|
|
1256
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1257
|
+
*/
|
|
1258
|
+
setArrayItems(id, itemDefs) {
|
|
1259
|
+
this.assertArrayObjField(id).items = itemDefs;
|
|
1260
|
+
return this;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Returns the sub-field list of an `array_obj` field.
|
|
1264
|
+
*
|
|
1265
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1266
|
+
*/
|
|
1267
|
+
getArrayItems(id) {
|
|
1268
|
+
return this.assertArrayObjField(id).items ?? [];
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Sets or clears the layout hint of an `array_obj` field.
|
|
1272
|
+
*
|
|
1273
|
+
* Presentation metadata only -- the engine never reads it. Passing
|
|
1274
|
+
* `undefined` removes the key, which reads as `'list'`.
|
|
1275
|
+
*
|
|
1276
|
+
* @throws If the id is not found or does not refer to an `array_obj` field.
|
|
1277
|
+
*/
|
|
1278
|
+
setArrayKind(id, kind) {
|
|
1279
|
+
const field = this.assertArrayObjField(id);
|
|
1280
|
+
if (kind === void 0) delete field.kind;
|
|
1281
|
+
else field.kind = kind;
|
|
1282
|
+
return this;
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Appends a sub-field to an `array_obj` field. The sub-field's `id` is
|
|
1286
|
+
* auto-assigned when omitted.
|
|
1287
|
+
*
|
|
1288
|
+
* @returns The id of the sub-field that was added.
|
|
1289
|
+
* @throws If the id is not found, does not refer to an `array_obj` field,
|
|
1290
|
+
* or the requested sub-field id is already taken.
|
|
1291
|
+
*/
|
|
1292
|
+
addArrayItemField(id, subField) {
|
|
1293
|
+
const field = this.assertArrayObjField(id);
|
|
1294
|
+
const subFieldId = subField.id ?? this.nextId();
|
|
1295
|
+
this.assertIdAvailable(subFieldId);
|
|
1296
|
+
field.items?.push({
|
|
1297
|
+
...subField,
|
|
1298
|
+
id: subFieldId
|
|
1299
|
+
});
|
|
1300
|
+
return subFieldId;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Merges properties into an existing `array_obj` sub-field.
|
|
1304
|
+
*
|
|
1305
|
+
* Like {@link updateField}, omitted properties are kept rather than
|
|
1306
|
+
* removed -- pass a property explicitly as `undefined` to clear it.
|
|
1307
|
+
*
|
|
1308
|
+
* @throws If the field or the sub-field is not found.
|
|
1309
|
+
*/
|
|
1310
|
+
updateArrayItemField(id, subFieldId, patch) {
|
|
1311
|
+
const subField = this.assertArrayObjField(id).items?.find((f) => f.id === subFieldId);
|
|
1312
|
+
if (!subField) throw new Error(`Sub-field with id ${subFieldId} not found in field ${id}`);
|
|
1313
|
+
Object.assign(subField, patch, { id: subFieldId });
|
|
1314
|
+
return this;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Removes a sub-field from an `array_obj` field.
|
|
1318
|
+
*
|
|
1319
|
+
* @throws If the field or the sub-field is not found.
|
|
1320
|
+
*/
|
|
1321
|
+
removeArrayItemField(id, subFieldId) {
|
|
1322
|
+
const field = this.assertArrayObjField(id);
|
|
1323
|
+
const index = field.items?.findIndex((f) => f.id === subFieldId) ?? -1;
|
|
1324
|
+
if (index < 0) throw new Error(`Sub-field with id ${subFieldId} not found in field ${id}`);
|
|
1325
|
+
field.items?.splice(index, 1);
|
|
1326
|
+
return this;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Moves a sub-field of an `array_obj` field to another position.
|
|
1330
|
+
*
|
|
1331
|
+
* @throws If the field is not found or either index is out of range.
|
|
1332
|
+
*/
|
|
1333
|
+
moveArrayItemField(id, fromIndex, toIndex) {
|
|
1334
|
+
const items = this.assertArrayObjField(id).items ?? [];
|
|
1335
|
+
if (fromIndex < 0 || fromIndex >= items.length) throw new Error(`Index ${fromIndex} out of range`);
|
|
1336
|
+
if (toIndex < 0 || toIndex >= items.length) throw new Error(`Index ${toIndex} out of range`);
|
|
1337
|
+
const [moved] = items.splice(fromIndex, 1);
|
|
1338
|
+
if (moved) items.splice(toIndex, 0, moved);
|
|
1339
|
+
return this;
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
977
1342
|
* Sets the label for a field.
|
|
978
1343
|
*/
|
|
979
1344
|
setLabel(id, label) {
|
|
@@ -985,6 +1350,18 @@ var FormDefinitionEditor = class {
|
|
|
985
1350
|
return this;
|
|
986
1351
|
}
|
|
987
1352
|
/**
|
|
1353
|
+
* Sets the displayed text of a `static` field.
|
|
1354
|
+
*
|
|
1355
|
+
* @throws If the id is not found or does not refer to a static field.
|
|
1356
|
+
*/
|
|
1357
|
+
setText(id, text) {
|
|
1358
|
+
const item = this.findItem(id);
|
|
1359
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1360
|
+
if (item.type !== "static") throw new Error(`Field ${id} is not a static field`);
|
|
1361
|
+
item.text = text;
|
|
1362
|
+
return this;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
988
1365
|
* Sets the description for a field or section.
|
|
989
1366
|
*/
|
|
990
1367
|
setFieldDescription(id, description) {
|
|
@@ -995,6 +1372,41 @@ var FormDefinitionEditor = class {
|
|
|
995
1372
|
return this;
|
|
996
1373
|
}
|
|
997
1374
|
/**
|
|
1375
|
+
* Sets the placeholder for a field or section.
|
|
1376
|
+
*
|
|
1377
|
+
* Passing `undefined` removes the property. Unlike {@link updateField},
|
|
1378
|
+
* which merges and therefore cannot clear, this genuinely deletes the key.
|
|
1379
|
+
*/
|
|
1380
|
+
setPlaceholder(id, placeholder) {
|
|
1381
|
+
return this.setMetadata(id, "placeholder", placeholder);
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Sets the answer guidelines for a field or section.
|
|
1385
|
+
*
|
|
1386
|
+
* Passing `undefined` removes the property.
|
|
1387
|
+
*/
|
|
1388
|
+
setAnswerGuidelines(id, answerGuidelines) {
|
|
1389
|
+
return this.setMetadata(id, "answer_guidelines", answerGuidelines);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Sets the external reference id for a field or section.
|
|
1393
|
+
*
|
|
1394
|
+
* Passing `undefined` removes the property.
|
|
1395
|
+
*/
|
|
1396
|
+
setReferenceId(id, referenceId) {
|
|
1397
|
+
return this.setMetadata(id, "reference_id", referenceId);
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Assigns or deletes one optional string property on a field or section.
|
|
1401
|
+
*/
|
|
1402
|
+
setMetadata(id, property, value) {
|
|
1403
|
+
const item = this.findItem(id);
|
|
1404
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1405
|
+
if (value === void 0) delete item[property];
|
|
1406
|
+
else item[property] = value;
|
|
1407
|
+
return this;
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
998
1410
|
* Returns a deep clone of the current form definition.
|
|
999
1411
|
*/
|
|
1000
1412
|
toJSON() {
|
|
@@ -1009,6 +1421,30 @@ var FormDefinitionEditor = class {
|
|
|
1009
1421
|
}
|
|
1010
1422
|
assertIdAvailable(id) {
|
|
1011
1423
|
if (this.findItem(id)) throw new Error(`Item with id ${id} already exists`);
|
|
1424
|
+
if (this.findArrayObjItem(id)) throw new Error(`Item with id ${id} already exists`);
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Finds an `array_obj` sub-field anywhere in the definition by its id.
|
|
1428
|
+
*/
|
|
1429
|
+
findArrayObjItem(id) {
|
|
1430
|
+
let found;
|
|
1431
|
+
this.walkAll(this.definition.content, (item) => {
|
|
1432
|
+
if (found || item.type !== "array_obj") return;
|
|
1433
|
+
const subField = (item.items ?? []).find((f) => f.id === id);
|
|
1434
|
+
if (subField) found = {
|
|
1435
|
+
field: item,
|
|
1436
|
+
subField
|
|
1437
|
+
};
|
|
1438
|
+
});
|
|
1439
|
+
return found;
|
|
1440
|
+
}
|
|
1441
|
+
assertArrayObjField(id) {
|
|
1442
|
+
const item = this.findItem(id);
|
|
1443
|
+
if (!item) throw new Error(`Item with id ${id} not found`);
|
|
1444
|
+
if (item.type !== "array_obj") throw new Error(`Field ${id} is not an array_obj field`);
|
|
1445
|
+
const field = item;
|
|
1446
|
+
if (!field.items) field.items = [];
|
|
1447
|
+
return field;
|
|
1012
1448
|
}
|
|
1013
1449
|
insertItem(item, parentId, index) {
|
|
1014
1450
|
const target = this.getTargetContent(parentId);
|
|
@@ -1220,6 +1656,11 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1220
1656
|
"properties": { "required": { "type": "boolean" } },
|
|
1221
1657
|
"additionalProperties": false
|
|
1222
1658
|
},
|
|
1659
|
+
"multiselectValidation": {
|
|
1660
|
+
"type": "object",
|
|
1661
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1662
|
+
"additionalProperties": false
|
|
1663
|
+
},
|
|
1223
1664
|
"arrayValidation": {
|
|
1224
1665
|
"type": "object",
|
|
1225
1666
|
"properties": {
|
|
@@ -1234,11 +1675,35 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1234
1675
|
},
|
|
1235
1676
|
"additionalProperties": false
|
|
1236
1677
|
},
|
|
1678
|
+
"arrayObjValidation": {
|
|
1679
|
+
"type": "object",
|
|
1680
|
+
"properties": {
|
|
1681
|
+
"minItems": {
|
|
1682
|
+
"type": "integer",
|
|
1683
|
+
"minimum": 0
|
|
1684
|
+
},
|
|
1685
|
+
"maxItems": {
|
|
1686
|
+
"type": "integer",
|
|
1687
|
+
"minimum": 1
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
"additionalProperties": false
|
|
1691
|
+
},
|
|
1237
1692
|
"fileValidation": {
|
|
1238
1693
|
"type": "object",
|
|
1239
1694
|
"properties": { "required": { "type": "boolean" } },
|
|
1240
1695
|
"additionalProperties": false
|
|
1241
1696
|
},
|
|
1697
|
+
"blockchainValidation": {
|
|
1698
|
+
"type": "object",
|
|
1699
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1700
|
+
"additionalProperties": false
|
|
1701
|
+
},
|
|
1702
|
+
"sustainabilityValidation": {
|
|
1703
|
+
"type": "object",
|
|
1704
|
+
"properties": { "required": { "type": "boolean" } },
|
|
1705
|
+
"additionalProperties": false
|
|
1706
|
+
},
|
|
1242
1707
|
"selectOption": {
|
|
1243
1708
|
"type": "object",
|
|
1244
1709
|
"required": ["value", "label"],
|
|
@@ -1255,6 +1720,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1255
1720
|
"type": { "const": "string" },
|
|
1256
1721
|
"label": { "type": "string" },
|
|
1257
1722
|
"description": { "type": "string" },
|
|
1723
|
+
"placeholder": { "type": "string" },
|
|
1724
|
+
"answer_guidelines": { "type": "string" },
|
|
1725
|
+
"reference_id": { "type": "string" },
|
|
1258
1726
|
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1259
1727
|
},
|
|
1260
1728
|
"additionalProperties": false
|
|
@@ -1266,6 +1734,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1266
1734
|
"type": { "const": "number" },
|
|
1267
1735
|
"label": { "type": "string" },
|
|
1268
1736
|
"description": { "type": "string" },
|
|
1737
|
+
"placeholder": { "type": "string" },
|
|
1738
|
+
"answer_guidelines": { "type": "string" },
|
|
1739
|
+
"reference_id": { "type": "string" },
|
|
1269
1740
|
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1270
1741
|
},
|
|
1271
1742
|
"additionalProperties": false
|
|
@@ -1277,6 +1748,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1277
1748
|
"type": { "const": "boolean" },
|
|
1278
1749
|
"label": { "type": "string" },
|
|
1279
1750
|
"description": { "type": "string" },
|
|
1751
|
+
"placeholder": { "type": "string" },
|
|
1752
|
+
"answer_guidelines": { "type": "string" },
|
|
1753
|
+
"reference_id": { "type": "string" },
|
|
1280
1754
|
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1281
1755
|
},
|
|
1282
1756
|
"additionalProperties": false
|
|
@@ -1288,6 +1762,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1288
1762
|
"type": { "const": "date" },
|
|
1289
1763
|
"label": { "type": "string" },
|
|
1290
1764
|
"description": { "type": "string" },
|
|
1765
|
+
"placeholder": { "type": "string" },
|
|
1766
|
+
"answer_guidelines": { "type": "string" },
|
|
1767
|
+
"reference_id": { "type": "string" },
|
|
1291
1768
|
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1292
1769
|
},
|
|
1293
1770
|
"additionalProperties": false
|
|
@@ -1303,6 +1780,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1303
1780
|
"type": { "const": "select" },
|
|
1304
1781
|
"label": { "type": "string" },
|
|
1305
1782
|
"description": { "type": "string" },
|
|
1783
|
+
"placeholder": { "type": "string" },
|
|
1784
|
+
"answer_guidelines": { "type": "string" },
|
|
1785
|
+
"reference_id": { "type": "string" },
|
|
1306
1786
|
"options": {
|
|
1307
1787
|
"type": "array",
|
|
1308
1788
|
"minItems": 1,
|
|
@@ -1319,21 +1799,337 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1319
1799
|
"type": { "const": "file" },
|
|
1320
1800
|
"label": { "type": "string" },
|
|
1321
1801
|
"description": { "type": "string" },
|
|
1802
|
+
"placeholder": { "type": "string" },
|
|
1803
|
+
"answer_guidelines": { "type": "string" },
|
|
1804
|
+
"reference_id": { "type": "string" },
|
|
1322
1805
|
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
1323
1806
|
},
|
|
1324
1807
|
"additionalProperties": false
|
|
1325
1808
|
},
|
|
1326
|
-
"
|
|
1327
|
-
{ "$ref": "#/$defs/arrayItemString" },
|
|
1328
|
-
{ "$ref": "#/$defs/arrayItemNumber" },
|
|
1329
|
-
{ "$ref": "#/$defs/arrayItemBoolean" },
|
|
1330
|
-
{ "$ref": "#/$defs/arrayItemDate" },
|
|
1331
|
-
{ "$ref": "#/$defs/arrayItemSelect" },
|
|
1332
|
-
{ "$ref": "#/$defs/arrayItemFile" }
|
|
1333
|
-
] },
|
|
1334
|
-
"stringField": {
|
|
1809
|
+
"arrayItemBlockchain": {
|
|
1335
1810
|
"type": "object",
|
|
1336
|
-
"required": [
|
|
1811
|
+
"required": ["type", "label"],
|
|
1812
|
+
"properties": {
|
|
1813
|
+
"type": { "const": "blockchain" },
|
|
1814
|
+
"label": { "type": "string" },
|
|
1815
|
+
"description": { "type": "string" },
|
|
1816
|
+
"placeholder": { "type": "string" },
|
|
1817
|
+
"answer_guidelines": { "type": "string" },
|
|
1818
|
+
"reference_id": { "type": "string" },
|
|
1819
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
1820
|
+
},
|
|
1821
|
+
"additionalProperties": false
|
|
1822
|
+
},
|
|
1823
|
+
"arrayItemSustainability": {
|
|
1824
|
+
"type": "object",
|
|
1825
|
+
"required": ["type", "label"],
|
|
1826
|
+
"properties": {
|
|
1827
|
+
"type": { "const": "sustainability" },
|
|
1828
|
+
"label": { "type": "string" },
|
|
1829
|
+
"description": { "type": "string" },
|
|
1830
|
+
"placeholder": { "type": "string" },
|
|
1831
|
+
"answer_guidelines": { "type": "string" },
|
|
1832
|
+
"reference_id": { "type": "string" },
|
|
1833
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
1834
|
+
},
|
|
1835
|
+
"additionalProperties": false
|
|
1836
|
+
},
|
|
1837
|
+
"arrayItemMultiselect": {
|
|
1838
|
+
"type": "object",
|
|
1839
|
+
"required": [
|
|
1840
|
+
"type",
|
|
1841
|
+
"label",
|
|
1842
|
+
"options"
|
|
1843
|
+
],
|
|
1844
|
+
"properties": {
|
|
1845
|
+
"type": { "const": "multiselect" },
|
|
1846
|
+
"label": { "type": "string" },
|
|
1847
|
+
"description": { "type": "string" },
|
|
1848
|
+
"placeholder": { "type": "string" },
|
|
1849
|
+
"answer_guidelines": { "type": "string" },
|
|
1850
|
+
"reference_id": { "type": "string" },
|
|
1851
|
+
"options": {
|
|
1852
|
+
"type": "array",
|
|
1853
|
+
"minItems": 1,
|
|
1854
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
1855
|
+
},
|
|
1856
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
1857
|
+
},
|
|
1858
|
+
"additionalProperties": false
|
|
1859
|
+
},
|
|
1860
|
+
"arrayItemStatic": {
|
|
1861
|
+
"type": "object",
|
|
1862
|
+
"required": ["type", "text"],
|
|
1863
|
+
"properties": {
|
|
1864
|
+
"type": { "const": "static" },
|
|
1865
|
+
"text": { "type": "string" },
|
|
1866
|
+
"label": { "type": "string" },
|
|
1867
|
+
"description": { "type": "string" },
|
|
1868
|
+
"placeholder": { "type": "string" },
|
|
1869
|
+
"answer_guidelines": { "type": "string" },
|
|
1870
|
+
"reference_id": { "type": "string" }
|
|
1871
|
+
},
|
|
1872
|
+
"additionalProperties": false
|
|
1873
|
+
},
|
|
1874
|
+
"arrayItem": { "oneOf": [
|
|
1875
|
+
{ "$ref": "#/$defs/arrayItemString" },
|
|
1876
|
+
{ "$ref": "#/$defs/arrayItemNumber" },
|
|
1877
|
+
{ "$ref": "#/$defs/arrayItemBoolean" },
|
|
1878
|
+
{ "$ref": "#/$defs/arrayItemDate" },
|
|
1879
|
+
{ "$ref": "#/$defs/arrayItemSelect" },
|
|
1880
|
+
{ "$ref": "#/$defs/arrayItemFile" },
|
|
1881
|
+
{ "$ref": "#/$defs/arrayItemBlockchain" },
|
|
1882
|
+
{ "$ref": "#/$defs/arrayItemSustainability" },
|
|
1883
|
+
{ "$ref": "#/$defs/arrayItemMultiselect" },
|
|
1884
|
+
{ "$ref": "#/$defs/arrayItemStatic" }
|
|
1885
|
+
] },
|
|
1886
|
+
"arrayObjItemString": {
|
|
1887
|
+
"type": "object",
|
|
1888
|
+
"required": [
|
|
1889
|
+
"id",
|
|
1890
|
+
"type",
|
|
1891
|
+
"label"
|
|
1892
|
+
],
|
|
1893
|
+
"properties": {
|
|
1894
|
+
"id": {
|
|
1895
|
+
"type": "integer",
|
|
1896
|
+
"minimum": 1
|
|
1897
|
+
},
|
|
1898
|
+
"type": { "const": "string" },
|
|
1899
|
+
"label": { "type": "string" },
|
|
1900
|
+
"description": { "type": "string" },
|
|
1901
|
+
"placeholder": { "type": "string" },
|
|
1902
|
+
"answer_guidelines": { "type": "string" },
|
|
1903
|
+
"reference_id": { "type": "string" },
|
|
1904
|
+
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1905
|
+
},
|
|
1906
|
+
"additionalProperties": false
|
|
1907
|
+
},
|
|
1908
|
+
"arrayObjItemNumber": {
|
|
1909
|
+
"type": "object",
|
|
1910
|
+
"required": [
|
|
1911
|
+
"id",
|
|
1912
|
+
"type",
|
|
1913
|
+
"label"
|
|
1914
|
+
],
|
|
1915
|
+
"properties": {
|
|
1916
|
+
"id": {
|
|
1917
|
+
"type": "integer",
|
|
1918
|
+
"minimum": 1
|
|
1919
|
+
},
|
|
1920
|
+
"type": { "const": "number" },
|
|
1921
|
+
"label": { "type": "string" },
|
|
1922
|
+
"description": { "type": "string" },
|
|
1923
|
+
"placeholder": { "type": "string" },
|
|
1924
|
+
"answer_guidelines": { "type": "string" },
|
|
1925
|
+
"reference_id": { "type": "string" },
|
|
1926
|
+
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1927
|
+
},
|
|
1928
|
+
"additionalProperties": false
|
|
1929
|
+
},
|
|
1930
|
+
"arrayObjItemBoolean": {
|
|
1931
|
+
"type": "object",
|
|
1932
|
+
"required": [
|
|
1933
|
+
"id",
|
|
1934
|
+
"type",
|
|
1935
|
+
"label"
|
|
1936
|
+
],
|
|
1937
|
+
"properties": {
|
|
1938
|
+
"id": {
|
|
1939
|
+
"type": "integer",
|
|
1940
|
+
"minimum": 1
|
|
1941
|
+
},
|
|
1942
|
+
"type": { "const": "boolean" },
|
|
1943
|
+
"label": { "type": "string" },
|
|
1944
|
+
"description": { "type": "string" },
|
|
1945
|
+
"placeholder": { "type": "string" },
|
|
1946
|
+
"answer_guidelines": { "type": "string" },
|
|
1947
|
+
"reference_id": { "type": "string" },
|
|
1948
|
+
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1949
|
+
},
|
|
1950
|
+
"additionalProperties": false
|
|
1951
|
+
},
|
|
1952
|
+
"arrayObjItemDate": {
|
|
1953
|
+
"type": "object",
|
|
1954
|
+
"required": [
|
|
1955
|
+
"id",
|
|
1956
|
+
"type",
|
|
1957
|
+
"label"
|
|
1958
|
+
],
|
|
1959
|
+
"properties": {
|
|
1960
|
+
"id": {
|
|
1961
|
+
"type": "integer",
|
|
1962
|
+
"minimum": 1
|
|
1963
|
+
},
|
|
1964
|
+
"type": { "const": "date" },
|
|
1965
|
+
"label": { "type": "string" },
|
|
1966
|
+
"description": { "type": "string" },
|
|
1967
|
+
"placeholder": { "type": "string" },
|
|
1968
|
+
"answer_guidelines": { "type": "string" },
|
|
1969
|
+
"reference_id": { "type": "string" },
|
|
1970
|
+
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1971
|
+
},
|
|
1972
|
+
"additionalProperties": false
|
|
1973
|
+
},
|
|
1974
|
+
"arrayObjItemSelect": {
|
|
1975
|
+
"type": "object",
|
|
1976
|
+
"required": [
|
|
1977
|
+
"id",
|
|
1978
|
+
"type",
|
|
1979
|
+
"label",
|
|
1980
|
+
"options"
|
|
1981
|
+
],
|
|
1982
|
+
"properties": {
|
|
1983
|
+
"id": {
|
|
1984
|
+
"type": "integer",
|
|
1985
|
+
"minimum": 1
|
|
1986
|
+
},
|
|
1987
|
+
"type": { "const": "select" },
|
|
1988
|
+
"label": { "type": "string" },
|
|
1989
|
+
"description": { "type": "string" },
|
|
1990
|
+
"placeholder": { "type": "string" },
|
|
1991
|
+
"answer_guidelines": { "type": "string" },
|
|
1992
|
+
"reference_id": { "type": "string" },
|
|
1993
|
+
"options": {
|
|
1994
|
+
"type": "array",
|
|
1995
|
+
"minItems": 1,
|
|
1996
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
1997
|
+
},
|
|
1998
|
+
"validation": { "$ref": "#/$defs/selectValidation" }
|
|
1999
|
+
},
|
|
2000
|
+
"additionalProperties": false
|
|
2001
|
+
},
|
|
2002
|
+
"arrayObjItemMultiselect": {
|
|
2003
|
+
"type": "object",
|
|
2004
|
+
"required": [
|
|
2005
|
+
"id",
|
|
2006
|
+
"type",
|
|
2007
|
+
"label",
|
|
2008
|
+
"options"
|
|
2009
|
+
],
|
|
2010
|
+
"properties": {
|
|
2011
|
+
"id": {
|
|
2012
|
+
"type": "integer",
|
|
2013
|
+
"minimum": 1
|
|
2014
|
+
},
|
|
2015
|
+
"type": { "const": "multiselect" },
|
|
2016
|
+
"label": { "type": "string" },
|
|
2017
|
+
"description": { "type": "string" },
|
|
2018
|
+
"placeholder": { "type": "string" },
|
|
2019
|
+
"answer_guidelines": { "type": "string" },
|
|
2020
|
+
"reference_id": { "type": "string" },
|
|
2021
|
+
"options": {
|
|
2022
|
+
"type": "array",
|
|
2023
|
+
"minItems": 1,
|
|
2024
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
2025
|
+
},
|
|
2026
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
2027
|
+
},
|
|
2028
|
+
"additionalProperties": false
|
|
2029
|
+
},
|
|
2030
|
+
"arrayObjItemFile": {
|
|
2031
|
+
"type": "object",
|
|
2032
|
+
"required": [
|
|
2033
|
+
"id",
|
|
2034
|
+
"type",
|
|
2035
|
+
"label"
|
|
2036
|
+
],
|
|
2037
|
+
"properties": {
|
|
2038
|
+
"id": {
|
|
2039
|
+
"type": "integer",
|
|
2040
|
+
"minimum": 1
|
|
2041
|
+
},
|
|
2042
|
+
"type": { "const": "file" },
|
|
2043
|
+
"label": { "type": "string" },
|
|
2044
|
+
"description": { "type": "string" },
|
|
2045
|
+
"placeholder": { "type": "string" },
|
|
2046
|
+
"answer_guidelines": { "type": "string" },
|
|
2047
|
+
"reference_id": { "type": "string" },
|
|
2048
|
+
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
2049
|
+
},
|
|
2050
|
+
"additionalProperties": false
|
|
2051
|
+
},
|
|
2052
|
+
"arrayObjItemBlockchain": {
|
|
2053
|
+
"type": "object",
|
|
2054
|
+
"required": [
|
|
2055
|
+
"id",
|
|
2056
|
+
"type",
|
|
2057
|
+
"label"
|
|
2058
|
+
],
|
|
2059
|
+
"properties": {
|
|
2060
|
+
"id": {
|
|
2061
|
+
"type": "integer",
|
|
2062
|
+
"minimum": 1
|
|
2063
|
+
},
|
|
2064
|
+
"type": { "const": "blockchain" },
|
|
2065
|
+
"label": { "type": "string" },
|
|
2066
|
+
"description": { "type": "string" },
|
|
2067
|
+
"placeholder": { "type": "string" },
|
|
2068
|
+
"answer_guidelines": { "type": "string" },
|
|
2069
|
+
"reference_id": { "type": "string" },
|
|
2070
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
2071
|
+
},
|
|
2072
|
+
"additionalProperties": false
|
|
2073
|
+
},
|
|
2074
|
+
"arrayObjItemSustainability": {
|
|
2075
|
+
"type": "object",
|
|
2076
|
+
"required": [
|
|
2077
|
+
"id",
|
|
2078
|
+
"type",
|
|
2079
|
+
"label"
|
|
2080
|
+
],
|
|
2081
|
+
"properties": {
|
|
2082
|
+
"id": {
|
|
2083
|
+
"type": "integer",
|
|
2084
|
+
"minimum": 1
|
|
2085
|
+
},
|
|
2086
|
+
"type": { "const": "sustainability" },
|
|
2087
|
+
"label": { "type": "string" },
|
|
2088
|
+
"description": { "type": "string" },
|
|
2089
|
+
"placeholder": { "type": "string" },
|
|
2090
|
+
"answer_guidelines": { "type": "string" },
|
|
2091
|
+
"reference_id": { "type": "string" },
|
|
2092
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
2093
|
+
},
|
|
2094
|
+
"additionalProperties": false
|
|
2095
|
+
},
|
|
2096
|
+
"arrayObjItemStatic": {
|
|
2097
|
+
"type": "object",
|
|
2098
|
+
"required": [
|
|
2099
|
+
"id",
|
|
2100
|
+
"type",
|
|
2101
|
+
"text"
|
|
2102
|
+
],
|
|
2103
|
+
"properties": {
|
|
2104
|
+
"id": {
|
|
2105
|
+
"type": "integer",
|
|
2106
|
+
"minimum": 1
|
|
2107
|
+
},
|
|
2108
|
+
"type": { "const": "static" },
|
|
2109
|
+
"text": { "type": "string" },
|
|
2110
|
+
"label": { "type": "string" },
|
|
2111
|
+
"description": { "type": "string" },
|
|
2112
|
+
"placeholder": { "type": "string" },
|
|
2113
|
+
"answer_guidelines": { "type": "string" },
|
|
2114
|
+
"reference_id": { "type": "string" }
|
|
2115
|
+
},
|
|
2116
|
+
"additionalProperties": false
|
|
2117
|
+
},
|
|
2118
|
+
"arrayObjItem": { "oneOf": [
|
|
2119
|
+
{ "$ref": "#/$defs/arrayObjItemString" },
|
|
2120
|
+
{ "$ref": "#/$defs/arrayObjItemNumber" },
|
|
2121
|
+
{ "$ref": "#/$defs/arrayObjItemBoolean" },
|
|
2122
|
+
{ "$ref": "#/$defs/arrayObjItemDate" },
|
|
2123
|
+
{ "$ref": "#/$defs/arrayObjItemSelect" },
|
|
2124
|
+
{ "$ref": "#/$defs/arrayObjItemMultiselect" },
|
|
2125
|
+
{ "$ref": "#/$defs/arrayObjItemFile" },
|
|
2126
|
+
{ "$ref": "#/$defs/arrayObjItemBlockchain" },
|
|
2127
|
+
{ "$ref": "#/$defs/arrayObjItemSustainability" },
|
|
2128
|
+
{ "$ref": "#/$defs/arrayObjItemStatic" }
|
|
2129
|
+
] },
|
|
2130
|
+
"stringField": {
|
|
2131
|
+
"type": "object",
|
|
2132
|
+
"required": [
|
|
1337
2133
|
"id",
|
|
1338
2134
|
"type",
|
|
1339
2135
|
"label"
|
|
@@ -1346,6 +2142,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1346
2142
|
"type": { "const": "string" },
|
|
1347
2143
|
"label": { "type": "string" },
|
|
1348
2144
|
"description": { "type": "string" },
|
|
2145
|
+
"placeholder": { "type": "string" },
|
|
2146
|
+
"answer_guidelines": { "type": "string" },
|
|
2147
|
+
"reference_id": { "type": "string" },
|
|
1349
2148
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1350
2149
|
"validation": { "$ref": "#/$defs/stringValidation" }
|
|
1351
2150
|
},
|
|
@@ -1366,6 +2165,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1366
2165
|
"type": { "const": "number" },
|
|
1367
2166
|
"label": { "type": "string" },
|
|
1368
2167
|
"description": { "type": "string" },
|
|
2168
|
+
"placeholder": { "type": "string" },
|
|
2169
|
+
"answer_guidelines": { "type": "string" },
|
|
2170
|
+
"reference_id": { "type": "string" },
|
|
1369
2171
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1370
2172
|
"validation": { "$ref": "#/$defs/numberValidation" }
|
|
1371
2173
|
},
|
|
@@ -1386,6 +2188,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1386
2188
|
"type": { "const": "boolean" },
|
|
1387
2189
|
"label": { "type": "string" },
|
|
1388
2190
|
"description": { "type": "string" },
|
|
2191
|
+
"placeholder": { "type": "string" },
|
|
2192
|
+
"answer_guidelines": { "type": "string" },
|
|
2193
|
+
"reference_id": { "type": "string" },
|
|
1389
2194
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1390
2195
|
"validation": { "$ref": "#/$defs/booleanValidation" }
|
|
1391
2196
|
},
|
|
@@ -1406,6 +2211,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1406
2211
|
"type": { "const": "date" },
|
|
1407
2212
|
"label": { "type": "string" },
|
|
1408
2213
|
"description": { "type": "string" },
|
|
2214
|
+
"placeholder": { "type": "string" },
|
|
2215
|
+
"answer_guidelines": { "type": "string" },
|
|
2216
|
+
"reference_id": { "type": "string" },
|
|
1409
2217
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1410
2218
|
"validation": { "$ref": "#/$defs/dateValidation" }
|
|
1411
2219
|
},
|
|
@@ -1427,6 +2235,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1427
2235
|
"type": { "const": "select" },
|
|
1428
2236
|
"label": { "type": "string" },
|
|
1429
2237
|
"description": { "type": "string" },
|
|
2238
|
+
"placeholder": { "type": "string" },
|
|
2239
|
+
"answer_guidelines": { "type": "string" },
|
|
2240
|
+
"reference_id": { "type": "string" },
|
|
1430
2241
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1431
2242
|
"options": {
|
|
1432
2243
|
"type": "array",
|
|
@@ -1437,6 +2248,58 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1437
2248
|
},
|
|
1438
2249
|
"additionalProperties": false
|
|
1439
2250
|
},
|
|
2251
|
+
"multiselectField": {
|
|
2252
|
+
"type": "object",
|
|
2253
|
+
"required": [
|
|
2254
|
+
"id",
|
|
2255
|
+
"type",
|
|
2256
|
+
"label",
|
|
2257
|
+
"options"
|
|
2258
|
+
],
|
|
2259
|
+
"properties": {
|
|
2260
|
+
"id": {
|
|
2261
|
+
"type": "integer",
|
|
2262
|
+
"minimum": 1
|
|
2263
|
+
},
|
|
2264
|
+
"type": { "const": "multiselect" },
|
|
2265
|
+
"label": { "type": "string" },
|
|
2266
|
+
"description": { "type": "string" },
|
|
2267
|
+
"placeholder": { "type": "string" },
|
|
2268
|
+
"answer_guidelines": { "type": "string" },
|
|
2269
|
+
"reference_id": { "type": "string" },
|
|
2270
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2271
|
+
"options": {
|
|
2272
|
+
"type": "array",
|
|
2273
|
+
"minItems": 1,
|
|
2274
|
+
"items": { "$ref": "#/$defs/selectOption" }
|
|
2275
|
+
},
|
|
2276
|
+
"validation": { "$ref": "#/$defs/multiselectValidation" }
|
|
2277
|
+
},
|
|
2278
|
+
"additionalProperties": false
|
|
2279
|
+
},
|
|
2280
|
+
"staticField": {
|
|
2281
|
+
"type": "object",
|
|
2282
|
+
"required": [
|
|
2283
|
+
"id",
|
|
2284
|
+
"type",
|
|
2285
|
+
"text"
|
|
2286
|
+
],
|
|
2287
|
+
"properties": {
|
|
2288
|
+
"id": {
|
|
2289
|
+
"type": "integer",
|
|
2290
|
+
"minimum": 1
|
|
2291
|
+
},
|
|
2292
|
+
"type": { "const": "static" },
|
|
2293
|
+
"text": { "type": "string" },
|
|
2294
|
+
"label": { "type": "string" },
|
|
2295
|
+
"description": { "type": "string" },
|
|
2296
|
+
"placeholder": { "type": "string" },
|
|
2297
|
+
"answer_guidelines": { "type": "string" },
|
|
2298
|
+
"reference_id": { "type": "string" },
|
|
2299
|
+
"condition": { "$ref": "#/$defs/condition" }
|
|
2300
|
+
},
|
|
2301
|
+
"additionalProperties": false
|
|
2302
|
+
},
|
|
1440
2303
|
"arrayField": {
|
|
1441
2304
|
"type": "object",
|
|
1442
2305
|
"required": [
|
|
@@ -1453,12 +2316,48 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1453
2316
|
"type": { "const": "array" },
|
|
1454
2317
|
"label": { "type": "string" },
|
|
1455
2318
|
"description": { "type": "string" },
|
|
2319
|
+
"placeholder": { "type": "string" },
|
|
2320
|
+
"answer_guidelines": { "type": "string" },
|
|
2321
|
+
"reference_id": { "type": "string" },
|
|
1456
2322
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1457
2323
|
"item": { "$ref": "#/$defs/arrayItem" },
|
|
1458
2324
|
"validation": { "$ref": "#/$defs/arrayValidation" }
|
|
1459
2325
|
},
|
|
1460
2326
|
"additionalProperties": false
|
|
1461
2327
|
},
|
|
2328
|
+
"arrayObjField": {
|
|
2329
|
+
"type": "object",
|
|
2330
|
+
"required": [
|
|
2331
|
+
"id",
|
|
2332
|
+
"type",
|
|
2333
|
+
"label",
|
|
2334
|
+
"items"
|
|
2335
|
+
],
|
|
2336
|
+
"properties": {
|
|
2337
|
+
"id": {
|
|
2338
|
+
"type": "integer",
|
|
2339
|
+
"minimum": 1
|
|
2340
|
+
},
|
|
2341
|
+
"type": { "const": "array_obj" },
|
|
2342
|
+
"label": { "type": "string" },
|
|
2343
|
+
"description": { "type": "string" },
|
|
2344
|
+
"placeholder": { "type": "string" },
|
|
2345
|
+
"answer_guidelines": { "type": "string" },
|
|
2346
|
+
"reference_id": { "type": "string" },
|
|
2347
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2348
|
+
"items": {
|
|
2349
|
+
"type": "array",
|
|
2350
|
+
"minItems": 1,
|
|
2351
|
+
"items": { "$ref": "#/$defs/arrayObjItem" }
|
|
2352
|
+
},
|
|
2353
|
+
"kind": {
|
|
2354
|
+
"type": "string",
|
|
2355
|
+
"enum": ["list", "table"]
|
|
2356
|
+
},
|
|
2357
|
+
"validation": { "$ref": "#/$defs/arrayObjValidation" }
|
|
2358
|
+
},
|
|
2359
|
+
"additionalProperties": false
|
|
2360
|
+
},
|
|
1462
2361
|
"fileField": {
|
|
1463
2362
|
"type": "object",
|
|
1464
2363
|
"required": [
|
|
@@ -1474,19 +2373,73 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1474
2373
|
"type": { "const": "file" },
|
|
1475
2374
|
"label": { "type": "string" },
|
|
1476
2375
|
"description": { "type": "string" },
|
|
2376
|
+
"placeholder": { "type": "string" },
|
|
2377
|
+
"answer_guidelines": { "type": "string" },
|
|
2378
|
+
"reference_id": { "type": "string" },
|
|
1477
2379
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1478
2380
|
"validation": { "$ref": "#/$defs/fileValidation" }
|
|
1479
2381
|
},
|
|
1480
2382
|
"additionalProperties": false
|
|
1481
2383
|
},
|
|
2384
|
+
"blockchainField": {
|
|
2385
|
+
"type": "object",
|
|
2386
|
+
"required": [
|
|
2387
|
+
"id",
|
|
2388
|
+
"type",
|
|
2389
|
+
"label"
|
|
2390
|
+
],
|
|
2391
|
+
"properties": {
|
|
2392
|
+
"id": {
|
|
2393
|
+
"type": "integer",
|
|
2394
|
+
"minimum": 1
|
|
2395
|
+
},
|
|
2396
|
+
"type": { "const": "blockchain" },
|
|
2397
|
+
"label": { "type": "string" },
|
|
2398
|
+
"description": { "type": "string" },
|
|
2399
|
+
"placeholder": { "type": "string" },
|
|
2400
|
+
"answer_guidelines": { "type": "string" },
|
|
2401
|
+
"reference_id": { "type": "string" },
|
|
2402
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2403
|
+
"validation": { "$ref": "#/$defs/blockchainValidation" }
|
|
2404
|
+
},
|
|
2405
|
+
"additionalProperties": false
|
|
2406
|
+
},
|
|
2407
|
+
"sustainabilityField": {
|
|
2408
|
+
"type": "object",
|
|
2409
|
+
"required": [
|
|
2410
|
+
"id",
|
|
2411
|
+
"type",
|
|
2412
|
+
"label"
|
|
2413
|
+
],
|
|
2414
|
+
"properties": {
|
|
2415
|
+
"id": {
|
|
2416
|
+
"type": "integer",
|
|
2417
|
+
"minimum": 1
|
|
2418
|
+
},
|
|
2419
|
+
"type": { "const": "sustainability" },
|
|
2420
|
+
"label": { "type": "string" },
|
|
2421
|
+
"description": { "type": "string" },
|
|
2422
|
+
"placeholder": { "type": "string" },
|
|
2423
|
+
"answer_guidelines": { "type": "string" },
|
|
2424
|
+
"reference_id": { "type": "string" },
|
|
2425
|
+
"condition": { "$ref": "#/$defs/condition" },
|
|
2426
|
+
"validation": { "$ref": "#/$defs/sustainabilityValidation" }
|
|
2427
|
+
},
|
|
2428
|
+
"additionalProperties": false
|
|
2429
|
+
},
|
|
1482
2430
|
"fieldItem": { "oneOf": [
|
|
1483
2431
|
{ "$ref": "#/$defs/stringField" },
|
|
1484
2432
|
{ "$ref": "#/$defs/numberField" },
|
|
1485
2433
|
{ "$ref": "#/$defs/booleanField" },
|
|
1486
2434
|
{ "$ref": "#/$defs/dateField" },
|
|
1487
2435
|
{ "$ref": "#/$defs/selectField" },
|
|
2436
|
+
{ "$ref": "#/$defs/multiselectField" },
|
|
1488
2437
|
{ "$ref": "#/$defs/arrayField" },
|
|
1489
|
-
{ "$ref": "#/$defs/
|
|
2438
|
+
{ "$ref": "#/$defs/arrayObjField" },
|
|
2439
|
+
{ "$ref": "#/$defs/fileField" },
|
|
2440
|
+
{ "$ref": "#/$defs/staticField" },
|
|
2441
|
+
{ "$ref": "#/$defs/blockchainField" },
|
|
2442
|
+
{ "$ref": "#/$defs/sustainabilityField" }
|
|
1490
2443
|
] },
|
|
1491
2444
|
"section": {
|
|
1492
2445
|
"type": "object",
|
|
@@ -1504,6 +2457,9 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1504
2457
|
"type": { "const": "section" },
|
|
1505
2458
|
"title": { "type": "string" },
|
|
1506
2459
|
"description": { "type": "string" },
|
|
2460
|
+
"placeholder": { "type": "string" },
|
|
2461
|
+
"answer_guidelines": { "type": "string" },
|
|
2462
|
+
"reference_id": { "type": "string" },
|
|
1507
2463
|
"condition": { "$ref": "#/$defs/condition" },
|
|
1508
2464
|
"content": {
|
|
1509
2465
|
"type": "array",
|
|
@@ -1516,6 +2472,228 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1516
2472
|
"contentItem": { "oneOf": [{ "$ref": "#/$defs/fieldItem" }, { "$ref": "#/$defs/section" }] }
|
|
1517
2473
|
}
|
|
1518
2474
|
});
|
|
2475
|
+
const itemRequiredProperties = new Map([
|
|
2476
|
+
["string", new Set([
|
|
2477
|
+
"id",
|
|
2478
|
+
"type",
|
|
2479
|
+
"label"
|
|
2480
|
+
])],
|
|
2481
|
+
["number", new Set([
|
|
2482
|
+
"id",
|
|
2483
|
+
"type",
|
|
2484
|
+
"label"
|
|
2485
|
+
])],
|
|
2486
|
+
["boolean", new Set([
|
|
2487
|
+
"id",
|
|
2488
|
+
"type",
|
|
2489
|
+
"label"
|
|
2490
|
+
])],
|
|
2491
|
+
["date", new Set([
|
|
2492
|
+
"id",
|
|
2493
|
+
"type",
|
|
2494
|
+
"label"
|
|
2495
|
+
])],
|
|
2496
|
+
["select", new Set([
|
|
2497
|
+
"id",
|
|
2498
|
+
"type",
|
|
2499
|
+
"label",
|
|
2500
|
+
"options"
|
|
2501
|
+
])],
|
|
2502
|
+
["multiselect", new Set([
|
|
2503
|
+
"id",
|
|
2504
|
+
"type",
|
|
2505
|
+
"label",
|
|
2506
|
+
"options"
|
|
2507
|
+
])],
|
|
2508
|
+
["array", new Set([
|
|
2509
|
+
"id",
|
|
2510
|
+
"type",
|
|
2511
|
+
"label",
|
|
2512
|
+
"item"
|
|
2513
|
+
])],
|
|
2514
|
+
["array_obj", new Set([
|
|
2515
|
+
"id",
|
|
2516
|
+
"type",
|
|
2517
|
+
"label",
|
|
2518
|
+
"items"
|
|
2519
|
+
])],
|
|
2520
|
+
["file", new Set([
|
|
2521
|
+
"id",
|
|
2522
|
+
"type",
|
|
2523
|
+
"label"
|
|
2524
|
+
])],
|
|
2525
|
+
["blockchain", new Set([
|
|
2526
|
+
"id",
|
|
2527
|
+
"type",
|
|
2528
|
+
"label"
|
|
2529
|
+
])],
|
|
2530
|
+
["sustainability", new Set([
|
|
2531
|
+
"id",
|
|
2532
|
+
"type",
|
|
2533
|
+
"label"
|
|
2534
|
+
])],
|
|
2535
|
+
["static", new Set([
|
|
2536
|
+
"id",
|
|
2537
|
+
"type",
|
|
2538
|
+
"text"
|
|
2539
|
+
])],
|
|
2540
|
+
["section", new Set([
|
|
2541
|
+
"id",
|
|
2542
|
+
"type",
|
|
2543
|
+
"title",
|
|
2544
|
+
"content"
|
|
2545
|
+
])]
|
|
2546
|
+
]);
|
|
2547
|
+
const itemAllowedProperties = new Map([
|
|
2548
|
+
["string", new Set([
|
|
2549
|
+
"id",
|
|
2550
|
+
"type",
|
|
2551
|
+
"label",
|
|
2552
|
+
"description",
|
|
2553
|
+
"placeholder",
|
|
2554
|
+
"answer_guidelines",
|
|
2555
|
+
"reference_id",
|
|
2556
|
+
"condition",
|
|
2557
|
+
"validation"
|
|
2558
|
+
])],
|
|
2559
|
+
["number", new Set([
|
|
2560
|
+
"id",
|
|
2561
|
+
"type",
|
|
2562
|
+
"label",
|
|
2563
|
+
"description",
|
|
2564
|
+
"placeholder",
|
|
2565
|
+
"answer_guidelines",
|
|
2566
|
+
"reference_id",
|
|
2567
|
+
"condition",
|
|
2568
|
+
"validation"
|
|
2569
|
+
])],
|
|
2570
|
+
["boolean", new Set([
|
|
2571
|
+
"id",
|
|
2572
|
+
"type",
|
|
2573
|
+
"label",
|
|
2574
|
+
"description",
|
|
2575
|
+
"placeholder",
|
|
2576
|
+
"answer_guidelines",
|
|
2577
|
+
"reference_id",
|
|
2578
|
+
"condition",
|
|
2579
|
+
"validation"
|
|
2580
|
+
])],
|
|
2581
|
+
["date", new Set([
|
|
2582
|
+
"id",
|
|
2583
|
+
"type",
|
|
2584
|
+
"label",
|
|
2585
|
+
"description",
|
|
2586
|
+
"placeholder",
|
|
2587
|
+
"answer_guidelines",
|
|
2588
|
+
"reference_id",
|
|
2589
|
+
"condition",
|
|
2590
|
+
"validation"
|
|
2591
|
+
])],
|
|
2592
|
+
["select", new Set([
|
|
2593
|
+
"id",
|
|
2594
|
+
"type",
|
|
2595
|
+
"label",
|
|
2596
|
+
"description",
|
|
2597
|
+
"placeholder",
|
|
2598
|
+
"answer_guidelines",
|
|
2599
|
+
"reference_id",
|
|
2600
|
+
"condition",
|
|
2601
|
+
"options",
|
|
2602
|
+
"validation"
|
|
2603
|
+
])],
|
|
2604
|
+
["multiselect", new Set([
|
|
2605
|
+
"id",
|
|
2606
|
+
"type",
|
|
2607
|
+
"label",
|
|
2608
|
+
"description",
|
|
2609
|
+
"placeholder",
|
|
2610
|
+
"answer_guidelines",
|
|
2611
|
+
"reference_id",
|
|
2612
|
+
"condition",
|
|
2613
|
+
"options",
|
|
2614
|
+
"validation"
|
|
2615
|
+
])],
|
|
2616
|
+
["array", new Set([
|
|
2617
|
+
"id",
|
|
2618
|
+
"type",
|
|
2619
|
+
"label",
|
|
2620
|
+
"description",
|
|
2621
|
+
"placeholder",
|
|
2622
|
+
"answer_guidelines",
|
|
2623
|
+
"reference_id",
|
|
2624
|
+
"condition",
|
|
2625
|
+
"item",
|
|
2626
|
+
"validation"
|
|
2627
|
+
])],
|
|
2628
|
+
["array_obj", new Set([
|
|
2629
|
+
"id",
|
|
2630
|
+
"type",
|
|
2631
|
+
"label",
|
|
2632
|
+
"description",
|
|
2633
|
+
"placeholder",
|
|
2634
|
+
"answer_guidelines",
|
|
2635
|
+
"reference_id",
|
|
2636
|
+
"condition",
|
|
2637
|
+
"items",
|
|
2638
|
+
"kind",
|
|
2639
|
+
"validation"
|
|
2640
|
+
])],
|
|
2641
|
+
["file", new Set([
|
|
2642
|
+
"id",
|
|
2643
|
+
"type",
|
|
2644
|
+
"label",
|
|
2645
|
+
"description",
|
|
2646
|
+
"placeholder",
|
|
2647
|
+
"answer_guidelines",
|
|
2648
|
+
"reference_id",
|
|
2649
|
+
"condition",
|
|
2650
|
+
"validation"
|
|
2651
|
+
])],
|
|
2652
|
+
["blockchain", new Set([
|
|
2653
|
+
"id",
|
|
2654
|
+
"type",
|
|
2655
|
+
"label",
|
|
2656
|
+
"description",
|
|
2657
|
+
"placeholder",
|
|
2658
|
+
"answer_guidelines",
|
|
2659
|
+
"reference_id",
|
|
2660
|
+
"condition",
|
|
2661
|
+
"validation"
|
|
2662
|
+
])],
|
|
2663
|
+
["sustainability", new Set([
|
|
2664
|
+
"id",
|
|
2665
|
+
"type",
|
|
2666
|
+
"label",
|
|
2667
|
+
"description",
|
|
2668
|
+
"placeholder",
|
|
2669
|
+
"answer_guidelines",
|
|
2670
|
+
"reference_id",
|
|
2671
|
+
"condition",
|
|
2672
|
+
"validation"
|
|
2673
|
+
])],
|
|
2674
|
+
["static", new Set([
|
|
2675
|
+
"id",
|
|
2676
|
+
"type",
|
|
2677
|
+
"text",
|
|
2678
|
+
"label",
|
|
2679
|
+
"description",
|
|
2680
|
+
"placeholder",
|
|
2681
|
+
"answer_guidelines",
|
|
2682
|
+
"reference_id",
|
|
2683
|
+
"condition"
|
|
2684
|
+
])],
|
|
2685
|
+
["section", new Set([
|
|
2686
|
+
"id",
|
|
2687
|
+
"type",
|
|
2688
|
+
"title",
|
|
2689
|
+
"description",
|
|
2690
|
+
"placeholder",
|
|
2691
|
+
"answer_guidelines",
|
|
2692
|
+
"reference_id",
|
|
2693
|
+
"condition",
|
|
2694
|
+
"content"
|
|
2695
|
+
])]
|
|
2696
|
+
]);
|
|
1519
2697
|
/**
|
|
1520
2698
|
* Validates form definitions at both the structural (JSON Schema) and
|
|
1521
2699
|
* semantic levels.
|
|
@@ -1529,12 +2707,17 @@ const validateFn = new Ajv2020({ allErrors: true }).compile({
|
|
|
1529
2707
|
* ### Semantic validation (`validate`)
|
|
1530
2708
|
* Checks for logical issues that go beyond JSON schema validity:
|
|
1531
2709
|
* 1. **Duplicate IDs** (`DUPLICATE_ID`) -- every content item id must be unique.
|
|
2710
|
+
* `array_obj` sub-field ids share that id space and are checked with it.
|
|
1532
2711
|
* 2. **Nesting depth** (`NESTING_DEPTH`) -- sections may not be nested more
|
|
1533
2712
|
* than 3 levels deep.
|
|
1534
2713
|
* 3. **Unknown field references** (`UNKNOWN_FIELD_REF`) -- conditions must
|
|
1535
2714
|
* only reference field ids that exist in the registry.
|
|
1536
|
-
* 4. **Condition references
|
|
1537
|
-
* must not reference section
|
|
2715
|
+
* 4. **Condition references a valueless item** (`CONDITION_REFS_SECTION`,
|
|
2716
|
+
* `CONDITION_REFS_STATIC`) -- conditions must not reference section or
|
|
2717
|
+
* static ids, because neither holds a value.
|
|
2718
|
+
* 4b. **Condition references an `array_obj` sub-field** (`CONDITION_REFS_ARRAY_OBJ_ITEM`)
|
|
2719
|
+
* -- a sub-field holds one value per row, so a form-level condition cannot
|
|
2720
|
+
* say which row it means.
|
|
1538
2721
|
* 5. **Constraint contradictions** (`INVALID_MIN_MAX`) -- e.g. `minLength > maxLength`,
|
|
1539
2722
|
* `min > max`, `minDate > maxDate` (absolute dates only), `minItems > maxItems`.
|
|
1540
2723
|
* 6. **Invalid regex** (`INVALID_REGEX`) -- string field `pattern` values must
|
|
@@ -1549,18 +2732,177 @@ var FormDefinitionValidator = class {
|
|
|
1549
2732
|
*/
|
|
1550
2733
|
validateSchema(input) {
|
|
1551
2734
|
if (validateFn(input)) return [];
|
|
1552
|
-
return (validateFn.errors ?? [])
|
|
2735
|
+
return this.formatSchemaErrors(validateFn.errors ?? [], input);
|
|
2736
|
+
}
|
|
2737
|
+
formatSchemaErrors(errors, input) {
|
|
2738
|
+
const issues = this.collectSchemaIssues(errors, input);
|
|
2739
|
+
const dedupedIssues = this.deduplicateSchemaIssues(issues);
|
|
2740
|
+
const specificIssuePaths = dedupedIssues.filter((issue) => issue.keyword !== "oneOf").map((issue) => issue.path);
|
|
2741
|
+
return dedupedIssues.filter((issue) => !this.isRedundantOneOfIssue(issue, specificIssuePaths)).map((issue) => ({
|
|
2742
|
+
code: "SCHEMA_INVALID",
|
|
2743
|
+
message: issue.message,
|
|
2744
|
+
params: {
|
|
2745
|
+
path: issue.path,
|
|
2746
|
+
keyword: issue.keyword,
|
|
2747
|
+
...issue.property ? { property: issue.property } : {}
|
|
2748
|
+
}
|
|
2749
|
+
}));
|
|
2750
|
+
}
|
|
2751
|
+
collectSchemaIssues(errors, input) {
|
|
2752
|
+
const additionalByPath = /* @__PURE__ */ new Map();
|
|
2753
|
+
const issues = [];
|
|
2754
|
+
for (const err of errors) {
|
|
1553
2755
|
const path = err.instancePath || "/";
|
|
1554
|
-
|
|
1555
|
-
if (err.keyword === "additionalProperties")
|
|
1556
|
-
|
|
1557
|
-
|
|
2756
|
+
if (!this.shouldKeepSchemaError(err, input)) continue;
|
|
2757
|
+
if (err.keyword === "additionalProperties") {
|
|
2758
|
+
const property = err.params.additionalProperty;
|
|
2759
|
+
if (!property) continue;
|
|
2760
|
+
if (!this.shouldKeepAdditionalPropertyError(path, property, input)) continue;
|
|
2761
|
+
const properties = additionalByPath.get(path) ?? /* @__PURE__ */ new Set();
|
|
2762
|
+
properties.add(property);
|
|
2763
|
+
additionalByPath.set(path, properties);
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
issues.push(this.formatSchemaIssue(err));
|
|
2767
|
+
}
|
|
2768
|
+
for (const [path, properties] of additionalByPath) issues.push(this.formatAdditionalPropertiesIssue(path, [...properties].sort()));
|
|
2769
|
+
return issues;
|
|
2770
|
+
}
|
|
2771
|
+
shouldKeepSchemaError(err, input) {
|
|
2772
|
+
if (err.keyword === "const" && this.getLastPathSegment(err.instancePath) === "type") {
|
|
2773
|
+
const value = this.getValueAtPath(input, this.getParentPath(err.instancePath));
|
|
2774
|
+
return !(this.isRecord(value) && typeof value.type === "string" && itemAllowedProperties.has(value.type));
|
|
2775
|
+
}
|
|
2776
|
+
if (err.keyword !== "required") return true;
|
|
2777
|
+
const missingProperty = err.params.missingProperty;
|
|
2778
|
+
if (!missingProperty) return true;
|
|
2779
|
+
const value = this.getValueAtPath(input, err.instancePath);
|
|
2780
|
+
if (this.isRecord(value) && value.type === void 0 && this.isContentItemPath(err.instancePath)) return missingProperty === "id" || missingProperty === "type";
|
|
2781
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
2782
|
+
const requiredProperties = itemRequiredProperties.get(value.type);
|
|
2783
|
+
return requiredProperties ? requiredProperties.has(missingProperty) : true;
|
|
2784
|
+
}
|
|
2785
|
+
shouldKeepAdditionalPropertyError(path, property, input) {
|
|
2786
|
+
const value = this.getValueAtPath(input, path);
|
|
2787
|
+
if (!this.isRecord(value) || typeof value.type !== "string") return true;
|
|
2788
|
+
const allowedProperties = itemAllowedProperties.get(value.type);
|
|
2789
|
+
return allowedProperties ? !allowedProperties.has(property) : true;
|
|
2790
|
+
}
|
|
2791
|
+
formatSchemaIssue(err) {
|
|
2792
|
+
const path = err.instancePath || "/";
|
|
2793
|
+
switch (err.keyword) {
|
|
2794
|
+
case "required": {
|
|
2795
|
+
const property = err.params.missingProperty ?? "unknown";
|
|
2796
|
+
return {
|
|
2797
|
+
path,
|
|
2798
|
+
keyword: err.keyword,
|
|
2799
|
+
property,
|
|
2800
|
+
message: `${this.formatPath(path)} is missing required property "${property}".`
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
case "const": {
|
|
2804
|
+
const propertyPath = this.formatPath(path);
|
|
2805
|
+
const parentPath = this.getParentPath(path);
|
|
2806
|
+
const property = this.getLastPathSegment(path);
|
|
2807
|
+
return {
|
|
2808
|
+
path,
|
|
2809
|
+
keyword: err.keyword,
|
|
2810
|
+
property,
|
|
2811
|
+
message: property === "type" ? `${this.formatPath(parentPath)} has an invalid type.` : `${propertyPath} has an invalid value.`
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
case "type": {
|
|
2815
|
+
const params = err.params;
|
|
2816
|
+
return {
|
|
2817
|
+
path,
|
|
2818
|
+
keyword: err.keyword,
|
|
2819
|
+
property: this.getLastPathSegment(path),
|
|
2820
|
+
message: `${this.formatPath(path)} must be ${this.formatArticle(params.type)} ${params.type ?? "valid value"}.`
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
case "oneOf": return {
|
|
2824
|
+
path,
|
|
2825
|
+
keyword: err.keyword,
|
|
2826
|
+
message: `${this.formatPath(path)} is invalid.`
|
|
1558
2827
|
};
|
|
1559
|
-
return {
|
|
1560
|
-
|
|
1561
|
-
|
|
2828
|
+
default: return {
|
|
2829
|
+
path,
|
|
2830
|
+
keyword: err.keyword,
|
|
2831
|
+
property: this.getLastPathSegment(path),
|
|
2832
|
+
message: `${this.formatPath(path)} ${err.message ?? "is invalid"}.`
|
|
1562
2833
|
};
|
|
1563
|
-
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
formatAdditionalPropertiesIssue(path, properties) {
|
|
2837
|
+
const propertyList = properties.map((property) => `"${property}"`).join(", ");
|
|
2838
|
+
const noun = properties.length === 1 ? "property" : "properties";
|
|
2839
|
+
return {
|
|
2840
|
+
path,
|
|
2841
|
+
keyword: "additionalProperties",
|
|
2842
|
+
property: properties.join(","),
|
|
2843
|
+
message: `${this.formatPath(path)} has unsupported ${noun}: ${propertyList}.`
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
deduplicateSchemaIssues(issues) {
|
|
2847
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2848
|
+
const result = [];
|
|
2849
|
+
for (const issue of issues) {
|
|
2850
|
+
const key = `${issue.path}:${issue.keyword}:${issue.property ?? ""}:${issue.message}`;
|
|
2851
|
+
if (seen.has(key)) continue;
|
|
2852
|
+
seen.add(key);
|
|
2853
|
+
result.push(issue);
|
|
2854
|
+
}
|
|
2855
|
+
return result;
|
|
2856
|
+
}
|
|
2857
|
+
isRedundantOneOfIssue(issue, specificIssuePaths) {
|
|
2858
|
+
if (issue.keyword !== "oneOf") return false;
|
|
2859
|
+
return specificIssuePaths.some((path) => path === issue.path || path.startsWith(`${issue.path}/`));
|
|
2860
|
+
}
|
|
2861
|
+
formatPath(path) {
|
|
2862
|
+
if (!path || path === "/") return "Form definition";
|
|
2863
|
+
const segments = path.split("/").filter(Boolean);
|
|
2864
|
+
const parts = [];
|
|
2865
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
2866
|
+
const segment = segments[index];
|
|
2867
|
+
const nextSegment = segments[index + 1];
|
|
2868
|
+
if (segment === void 0) continue;
|
|
2869
|
+
if (segment === "content" && nextSegment !== void 0 && /^\d+$/.test(nextSegment)) {
|
|
2870
|
+
parts.push(`${parts.length === 0 ? "Content" : "content"} item ${Number(nextSegment) + 1}`);
|
|
2871
|
+
index += 1;
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
if (segment === "validation" && parts.length > 0) {
|
|
2875
|
+
parts[parts.length - 1] = `${parts[parts.length - 1]} validation`;
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
parts.push(segment);
|
|
2879
|
+
}
|
|
2880
|
+
return parts.join(" > ");
|
|
2881
|
+
}
|
|
2882
|
+
getValueAtPath(input, path) {
|
|
2883
|
+
if (!path) return input;
|
|
2884
|
+
return path.split("/").filter(Boolean).reduce((value, segment) => {
|
|
2885
|
+
if (Array.isArray(value)) return value[Number(segment)];
|
|
2886
|
+
if (this.isRecord(value)) return value[segment];
|
|
2887
|
+
}, input);
|
|
2888
|
+
}
|
|
2889
|
+
getParentPath(path) {
|
|
2890
|
+
const segments = path.split("/").filter(Boolean);
|
|
2891
|
+
return segments.length > 1 ? `/${segments.slice(0, -1).join("/")}` : "/";
|
|
2892
|
+
}
|
|
2893
|
+
getLastPathSegment(path) {
|
|
2894
|
+
return path.split("/").filter(Boolean).at(-1);
|
|
2895
|
+
}
|
|
2896
|
+
isContentItemPath(path) {
|
|
2897
|
+
const segments = path.split("/").filter(Boolean);
|
|
2898
|
+
return segments.at(-2) === "content" && /^\d+$/.test(segments.at(-1) ?? "");
|
|
2899
|
+
}
|
|
2900
|
+
formatArticle(value) {
|
|
2901
|
+
if (!value) return "a";
|
|
2902
|
+
return /^[aeiou]/i.test(value) ? "an" : "a";
|
|
2903
|
+
}
|
|
2904
|
+
isRecord(value) {
|
|
2905
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1564
2906
|
}
|
|
1565
2907
|
/**
|
|
1566
2908
|
* Validates a form definition semantically.
|
|
@@ -1573,23 +2915,52 @@ var FormDefinitionValidator = class {
|
|
|
1573
2915
|
const issues = [];
|
|
1574
2916
|
this.checkDuplicateIds(definition.content, issues);
|
|
1575
2917
|
this.checkNestingDepth(definition.content, 0, issues);
|
|
1576
|
-
this.
|
|
1577
|
-
this.
|
|
2918
|
+
const arrayObjItemIds = this.collectArrayObjItemIds(definition.content);
|
|
2919
|
+
this.checkConditionRefsArrayObjItem(registry, arrayObjItemIds, issues);
|
|
2920
|
+
this.checkConditionRefs(registry, arrayObjItemIds, issues);
|
|
2921
|
+
this.checkConditionRefsValueless(registry, issues);
|
|
1578
2922
|
this.checkConstraintContradictions(registry, issues);
|
|
1579
2923
|
this.checkInvalidRegex(registry, issues);
|
|
1580
2924
|
return issues;
|
|
1581
2925
|
}
|
|
1582
2926
|
checkDuplicateIds(content, issues) {
|
|
1583
2927
|
const seen = /* @__PURE__ */ new Set();
|
|
1584
|
-
|
|
1585
|
-
if (seen.has(
|
|
2928
|
+
const record = (id) => {
|
|
2929
|
+
if (seen.has(id)) issues.push({
|
|
1586
2930
|
code: "DUPLICATE_ID",
|
|
1587
|
-
message: `Duplicate id: ${
|
|
1588
|
-
itemId:
|
|
2931
|
+
message: `Duplicate id: ${id}`,
|
|
2932
|
+
itemId: id
|
|
1589
2933
|
});
|
|
1590
|
-
else seen.add(
|
|
2934
|
+
else seen.add(id);
|
|
2935
|
+
};
|
|
2936
|
+
this.walkItems(content, (item) => {
|
|
2937
|
+
record(item.id);
|
|
2938
|
+
if (item.type === "array_obj") for (const subField of item.items ?? []) record(subField.id);
|
|
1591
2939
|
});
|
|
1592
2940
|
}
|
|
2941
|
+
collectArrayObjItemIds(content) {
|
|
2942
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2943
|
+
this.walkItems(content, (item) => {
|
|
2944
|
+
if (item.type !== "array_obj") return;
|
|
2945
|
+
for (const subField of item.items ?? []) if (!ids.has(subField.id)) ids.set(subField.id, item.id);
|
|
2946
|
+
});
|
|
2947
|
+
return ids;
|
|
2948
|
+
}
|
|
2949
|
+
checkConditionRefsArrayObjItem(registry, arrayObjItemIds, issues) {
|
|
2950
|
+
if (arrayObjItemIds.size === 0) return;
|
|
2951
|
+
for (const [id, entry] of registry) {
|
|
2952
|
+
if (!entry.condition) continue;
|
|
2953
|
+
for (const ref of DependencyGraph.extractFieldRefs(entry.condition)) {
|
|
2954
|
+
const containerId = arrayObjItemIds.get(ref);
|
|
2955
|
+
if (containerId === void 0) continue;
|
|
2956
|
+
issues.push({
|
|
2957
|
+
code: "CONDITION_REFS_ARRAY_OBJ_ITEM",
|
|
2958
|
+
message: `Condition references sub-field ${ref} of array_obj field ${containerId}, whose value is per-row and not addressable (in item ${id})`,
|
|
2959
|
+
itemId: id
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
1593
2964
|
checkNestingDepth(content, depth, issues) {
|
|
1594
2965
|
for (const item of content) if (item.type === "section") if (depth >= 3) issues.push({
|
|
1595
2966
|
code: "NESTING_DEPTH",
|
|
@@ -1598,28 +2969,37 @@ var FormDefinitionValidator = class {
|
|
|
1598
2969
|
});
|
|
1599
2970
|
else this.checkNestingDepth(item.content, depth + 1, issues);
|
|
1600
2971
|
}
|
|
1601
|
-
checkConditionRefs(registry, issues) {
|
|
2972
|
+
checkConditionRefs(registry, arrayObjItemIds, issues) {
|
|
1602
2973
|
for (const [id, entry] of registry) {
|
|
1603
2974
|
if (!entry.condition) continue;
|
|
1604
2975
|
const refs = DependencyGraph.extractFieldRefs(entry.condition);
|
|
1605
|
-
for (const ref of refs)
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
2976
|
+
for (const ref of refs) {
|
|
2977
|
+
if (arrayObjItemIds.has(ref)) continue;
|
|
2978
|
+
if (!registry.has(ref)) issues.push({
|
|
2979
|
+
code: "UNKNOWN_FIELD_REF",
|
|
2980
|
+
message: `Condition references unknown field: ${ref} (in item ${id})`,
|
|
2981
|
+
itemId: id
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
1610
2984
|
}
|
|
1611
2985
|
}
|
|
1612
|
-
|
|
2986
|
+
checkConditionRefsValueless(registry, issues) {
|
|
1613
2987
|
for (const [id, entry] of registry) {
|
|
1614
2988
|
if (!entry.condition) continue;
|
|
1615
2989
|
const refs = DependencyGraph.extractFieldRefs(entry.condition);
|
|
1616
2990
|
for (const ref of refs) {
|
|
1617
2991
|
const refEntry = registry.get(ref);
|
|
1618
|
-
if (refEntry
|
|
2992
|
+
if (!refEntry) continue;
|
|
2993
|
+
if (refEntry.type === "section") issues.push({
|
|
1619
2994
|
code: "CONDITION_REFS_SECTION",
|
|
1620
2995
|
message: `Condition references section ${ref}, which has no value (in item ${id})`,
|
|
1621
2996
|
itemId: id
|
|
1622
2997
|
});
|
|
2998
|
+
if (refEntry.type === "static") issues.push({
|
|
2999
|
+
code: "CONDITION_REFS_STATIC",
|
|
3000
|
+
message: `Condition references static field ${ref}, which has no value (in item ${id})`,
|
|
3001
|
+
itemId: id
|
|
3002
|
+
});
|
|
1623
3003
|
}
|
|
1624
3004
|
}
|
|
1625
3005
|
}
|
|
@@ -1660,7 +3040,8 @@ var FormDefinitionValidator = class {
|
|
|
1660
3040
|
}
|
|
1661
3041
|
break;
|
|
1662
3042
|
}
|
|
1663
|
-
case "array":
|
|
3043
|
+
case "array":
|
|
3044
|
+
case "array_obj": {
|
|
1664
3045
|
const v = entry.validation;
|
|
1665
3046
|
if (v.minItems !== void 0 && v.maxItems !== void 0 && v.maxItems < v.minItems) issues.push({
|
|
1666
3047
|
code: "INVALID_MIN_MAX",
|
|
@@ -1746,6 +3127,7 @@ var VisibilityResolver = class {
|
|
|
1746
3127
|
registry;
|
|
1747
3128
|
conditionEvaluator;
|
|
1748
3129
|
topologicalOrder;
|
|
3130
|
+
fieldTypes;
|
|
1749
3131
|
/**
|
|
1750
3132
|
* @param registry - The engine's field registry.
|
|
1751
3133
|
* @param conditionEvaluator - Evaluator for condition trees.
|
|
@@ -1755,6 +3137,7 @@ var VisibilityResolver = class {
|
|
|
1755
3137
|
this.registry = registry;
|
|
1756
3138
|
this.conditionEvaluator = conditionEvaluator;
|
|
1757
3139
|
this.topologicalOrder = topologicalOrder;
|
|
3140
|
+
this.fieldTypes = new Map([...registry].map(([id, entry]) => [id, entry.type]));
|
|
1758
3141
|
}
|
|
1759
3142
|
/**
|
|
1760
3143
|
* Determines whether a single field or section is visible.
|
|
@@ -1781,7 +3164,8 @@ var VisibilityResolver = class {
|
|
|
1781
3164
|
if (entry.condition) {
|
|
1782
3165
|
if (!this.conditionEvaluator.evalCondition(entry.condition, {
|
|
1783
3166
|
values,
|
|
1784
|
-
now
|
|
3167
|
+
now,
|
|
3168
|
+
fieldTypes: this.fieldTypes
|
|
1785
3169
|
})) return false;
|
|
1786
3170
|
}
|
|
1787
3171
|
if (entry.parentId !== void 0) return this.isVisible(entry.parentId, values, now);
|
|
@@ -1819,7 +3203,8 @@ var VisibilityResolver = class {
|
|
|
1819
3203
|
const visible = this.conditionEvaluator.evalCondition(entry.condition, {
|
|
1820
3204
|
values,
|
|
1821
3205
|
visibilityMap: result,
|
|
1822
|
-
now
|
|
3206
|
+
now,
|
|
3207
|
+
fieldTypes: this.fieldTypes
|
|
1823
3208
|
});
|
|
1824
3209
|
result.set(id, visible);
|
|
1825
3210
|
} else result.set(id, true);
|
|
@@ -2086,14 +3471,16 @@ var FormEngine = class FormEngine {
|
|
|
2086
3471
|
}
|
|
2087
3472
|
static walkContent(content, parentId, registry, contentOrder) {
|
|
2088
3473
|
for (const item of content) {
|
|
3474
|
+
const isValueField = item.type !== "section" && item.type !== "static";
|
|
2089
3475
|
const entry = {
|
|
2090
3476
|
id: item.id,
|
|
2091
3477
|
type: item.type,
|
|
2092
3478
|
condition: item.condition,
|
|
2093
|
-
validation:
|
|
3479
|
+
validation: isValueField ? item.validation : void 0,
|
|
2094
3480
|
parentId,
|
|
2095
|
-
options: item.type === "select" ? item.options : void 0,
|
|
3481
|
+
options: item.type === "select" || item.type === "multiselect" ? item.options : void 0,
|
|
2096
3482
|
item: item.type === "array" ? item.item : void 0,
|
|
3483
|
+
items: item.type === "array_obj" ? item.items : void 0,
|
|
2097
3484
|
label: item.type !== "section" ? item.label : void 0,
|
|
2098
3485
|
title: item.type === "section" ? item.title : void 0
|
|
2099
3486
|
};
|
|
@@ -2104,6 +3491,108 @@ var FormEngine = class FormEngine {
|
|
|
2104
3491
|
}
|
|
2105
3492
|
};
|
|
2106
3493
|
//#endregion
|
|
3494
|
+
//#region src/suggestions.ts
|
|
3495
|
+
/**
|
|
3496
|
+
* Pure helpers for reading and writing the `suggestions` block of a
|
|
3497
|
+
* {@link FormDocument}.
|
|
3498
|
+
*
|
|
3499
|
+
* Every function is total over legacy documents: `doc.suggestions` may be
|
|
3500
|
+
* `undefined` (documents written before suggestions existed) and each helper
|
|
3501
|
+
* treats that as "no suggestions". Writers never mutate their input -- they
|
|
3502
|
+
* return a new document -- and they drop the `suggestions` key entirely once it
|
|
3503
|
+
* would be empty, so a document that never had suggestions round-trips
|
|
3504
|
+
* unchanged.
|
|
3505
|
+
*
|
|
3506
|
+
* These helpers deliberately do **not** check that `fieldId` refers to an
|
|
3507
|
+
* existing, non-section field. That check belongs to the general-purpose
|
|
3508
|
+
* authoring API ({@link FormValuesEditor}); callers that already resolved a
|
|
3509
|
+
* field -- such as the viewer's render loop -- would only pay for it twice.
|
|
3510
|
+
*/
|
|
3511
|
+
/**
|
|
3512
|
+
* Returns the suggestion recorded for a field.
|
|
3513
|
+
*
|
|
3514
|
+
* @param doc - The form document to read from.
|
|
3515
|
+
* @param fieldId - Numeric id of the field.
|
|
3516
|
+
* @returns The suggestion, or `undefined` when the field has none.
|
|
3517
|
+
*/
|
|
3518
|
+
const getSuggestion = (doc, fieldId) => doc.suggestions?.[String(fieldId)];
|
|
3519
|
+
/**
|
|
3520
|
+
* Records a suggestion for a field, replacing any existing one.
|
|
3521
|
+
*
|
|
3522
|
+
* @param doc - The form document to update.
|
|
3523
|
+
* @param fieldId - Numeric id of the field.
|
|
3524
|
+
* @param suggestion - The suggestion to store. Deep-copied before storing.
|
|
3525
|
+
* @returns A new document carrying the suggestion.
|
|
3526
|
+
* @throws If `confidence` is not a finite number within `0..1`.
|
|
3527
|
+
*/
|
|
3528
|
+
const setSuggestion = (doc, fieldId, suggestion) => {
|
|
3529
|
+
const { confidence } = suggestion;
|
|
3530
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new Error(`Suggestion confidence must be a number between 0 and 1, received ${confidence}`);
|
|
3531
|
+
return {
|
|
3532
|
+
...doc,
|
|
3533
|
+
suggestions: {
|
|
3534
|
+
...doc.suggestions,
|
|
3535
|
+
[String(fieldId)]: deepCopy(suggestion)
|
|
3536
|
+
}
|
|
3537
|
+
};
|
|
3538
|
+
};
|
|
3539
|
+
/**
|
|
3540
|
+
* Removes the suggestion recorded for a field, leaving the field's value alone.
|
|
3541
|
+
*
|
|
3542
|
+
* When the removed entry was the last one, the `suggestions` key is dropped
|
|
3543
|
+
* from the document rather than left as an empty object.
|
|
3544
|
+
*
|
|
3545
|
+
* @param doc - The form document to update.
|
|
3546
|
+
* @param fieldId - Numeric id of the field.
|
|
3547
|
+
* @returns A new document without that suggestion, or `doc` itself when there
|
|
3548
|
+
* was nothing to remove.
|
|
3549
|
+
*/
|
|
3550
|
+
const removeSuggestion = (doc, fieldId) => {
|
|
3551
|
+
const key = String(fieldId);
|
|
3552
|
+
if (!doc.suggestions || !(key in doc.suggestions)) return doc;
|
|
3553
|
+
const { [key]: _removed, ...rest } = doc.suggestions;
|
|
3554
|
+
if (Object.keys(rest).length === 0) {
|
|
3555
|
+
const { suggestions: _dropped, ...withoutSuggestions } = doc;
|
|
3556
|
+
return withoutSuggestions;
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
...doc,
|
|
3560
|
+
suggestions: rest
|
|
3561
|
+
};
|
|
3562
|
+
};
|
|
3563
|
+
/**
|
|
3564
|
+
* Copies a field's suggested value into the document's values, keeping the
|
|
3565
|
+
* suggestion in place so the origin of the answer stays visible.
|
|
3566
|
+
*
|
|
3567
|
+
* The value is deep-copied: array and object suggestions must not become
|
|
3568
|
+
* aliases of the stored value, or later in-place edits (e.g. appending an array
|
|
3569
|
+
* item) would silently rewrite the suggestion too.
|
|
3570
|
+
*
|
|
3571
|
+
* @param doc - The form document to update.
|
|
3572
|
+
* @param fieldId - Numeric id of the field.
|
|
3573
|
+
* @returns A new document with the value applied, or `doc` itself when the
|
|
3574
|
+
* field has no suggestion.
|
|
3575
|
+
*/
|
|
3576
|
+
const applySuggestion = (doc, fieldId) => {
|
|
3577
|
+
const suggestion = getSuggestion(doc, fieldId);
|
|
3578
|
+
if (!suggestion) return doc;
|
|
3579
|
+
return {
|
|
3580
|
+
...doc,
|
|
3581
|
+
values: {
|
|
3582
|
+
...doc.values,
|
|
3583
|
+
[String(fieldId)]: deepCopy(suggestion.value)
|
|
3584
|
+
}
|
|
3585
|
+
};
|
|
3586
|
+
};
|
|
3587
|
+
/**
|
|
3588
|
+
* Returns all suggestions on a document as a plain object, never `undefined`.
|
|
3589
|
+
*
|
|
3590
|
+
* @param doc - The form document to read from.
|
|
3591
|
+
* @returns A copy of the suggestions map. Empty when the document has none.
|
|
3592
|
+
*/
|
|
3593
|
+
const getSuggestions = (doc) => ({ ...doc.suggestions });
|
|
3594
|
+
const deepCopy = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
3595
|
+
//#endregion
|
|
2107
3596
|
//#region src/form-values-editor.ts
|
|
2108
3597
|
/**
|
|
2109
3598
|
* Mutable editor for building and modifying form values against a {@link FormDefinition}.
|
|
@@ -2175,13 +3664,16 @@ var FormValuesEditor = class {
|
|
|
2175
3664
|
* If the field currently has no value, it is initialized to an empty array
|
|
2176
3665
|
* before appending.
|
|
2177
3666
|
*
|
|
2178
|
-
* @param fieldId - Numeric id of the array field.
|
|
2179
|
-
* @param value - The value to append. Defaults to `undefined
|
|
3667
|
+
* @param fieldId - Numeric id of the `array` or `array_obj` field.
|
|
3668
|
+
* @param value - The value to append. Defaults to `undefined` for an
|
|
3669
|
+
* `array` field and to an empty row (`{}`) for an `array_obj` field.
|
|
2180
3670
|
* @returns `this` for chaining.
|
|
2181
3671
|
* @throws If `fieldId` is not an array field.
|
|
2182
3672
|
*/
|
|
2183
3673
|
addArrayItem(fieldId, value) {
|
|
2184
|
-
this.getOrInitArray(fieldId)
|
|
3674
|
+
const arr = this.getOrInitArray(fieldId);
|
|
3675
|
+
const entry = this.engine.getFieldDef(fieldId);
|
|
3676
|
+
arr.push(value === void 0 && entry?.type === "array_obj" ? {} : value);
|
|
2185
3677
|
return this;
|
|
2186
3678
|
}
|
|
2187
3679
|
/**
|
|
@@ -2231,6 +3723,114 @@ var FormValuesEditor = class {
|
|
|
2231
3723
|
return this;
|
|
2232
3724
|
}
|
|
2233
3725
|
/**
|
|
3726
|
+
* Returns the value of one sub-field within one row of an `array_obj` field.
|
|
3727
|
+
*
|
|
3728
|
+
* @param fieldId - Numeric id of the `array_obj` field.
|
|
3729
|
+
* @param index - Zero-based row index.
|
|
3730
|
+
* @param subFieldId - Numeric id of the sub-field.
|
|
3731
|
+
* @returns The sub-field value, or `undefined` if not set.
|
|
3732
|
+
* @throws If the field is not an `array_obj`, the row index is out of
|
|
3733
|
+
* bounds, or the sub-field does not belong to the field.
|
|
3734
|
+
*/
|
|
3735
|
+
getArrayObjValue(fieldId, index, subFieldId) {
|
|
3736
|
+
return this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)];
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Sets the value of one sub-field within one row of an `array_obj` field.
|
|
3740
|
+
*
|
|
3741
|
+
* @param fieldId - Numeric id of the `array_obj` field.
|
|
3742
|
+
* @param index - Zero-based row index.
|
|
3743
|
+
* @param subFieldId - Numeric id of the sub-field.
|
|
3744
|
+
* @param value - The value to set.
|
|
3745
|
+
* @returns `this` for chaining.
|
|
3746
|
+
* @throws If the field is not an `array_obj`, the row index is out of
|
|
3747
|
+
* bounds, or the sub-field does not belong to the field or is static.
|
|
3748
|
+
*/
|
|
3749
|
+
setArrayObjValue(fieldId, index, subFieldId, value) {
|
|
3750
|
+
this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)] = value;
|
|
3751
|
+
return this;
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* Removes one sub-field's value from one row of an `array_obj` field.
|
|
3755
|
+
*
|
|
3756
|
+
* @returns `this` for chaining.
|
|
3757
|
+
* @throws Under the same conditions as {@link setArrayObjValue}.
|
|
3758
|
+
*/
|
|
3759
|
+
clearArrayObjValue(fieldId, index, subFieldId) {
|
|
3760
|
+
delete this.assertArrayObjRow(fieldId, index, subFieldId)[String(subFieldId)];
|
|
3761
|
+
return this;
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Returns the suggestion recorded for a field.
|
|
3765
|
+
*
|
|
3766
|
+
* @param fieldId - Numeric id of the field.
|
|
3767
|
+
* @returns The suggestion, or `undefined` if the field has none.
|
|
3768
|
+
*/
|
|
3769
|
+
getSuggestion(fieldId) {
|
|
3770
|
+
return getSuggestion(this.doc, fieldId);
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Returns every suggestion on the document, keyed by stringified field id.
|
|
3774
|
+
*
|
|
3775
|
+
* @returns A copy of the suggestions map. Empty when there are none.
|
|
3776
|
+
*/
|
|
3777
|
+
getSuggestions() {
|
|
3778
|
+
return getSuggestions(this.doc);
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Records a suggestion for a field, replacing any existing one. The field's
|
|
3782
|
+
* value is left untouched.
|
|
3783
|
+
*
|
|
3784
|
+
* @param fieldId - Numeric id of the field.
|
|
3785
|
+
* @param suggestion - The suggestion to store.
|
|
3786
|
+
* @returns `this` for chaining.
|
|
3787
|
+
* @throws If `fieldId` is unknown, references a section, or `confidence`
|
|
3788
|
+
* falls outside `0..1`.
|
|
3789
|
+
*/
|
|
3790
|
+
setSuggestion(fieldId, suggestion) {
|
|
3791
|
+
this.assertField(fieldId);
|
|
3792
|
+
this.doc = setSuggestion(this.doc, fieldId, suggestion);
|
|
3793
|
+
return this;
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Removes the suggestion recorded for a field, leaving its value untouched.
|
|
3797
|
+
*
|
|
3798
|
+
* @param fieldId - Numeric id of the field.
|
|
3799
|
+
* @returns `this` for chaining.
|
|
3800
|
+
*/
|
|
3801
|
+
clearSuggestion(fieldId) {
|
|
3802
|
+
this.doc = removeSuggestion(this.doc, fieldId);
|
|
3803
|
+
return this;
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Accepts a field's suggestion: copies the suggested value into the field's
|
|
3807
|
+
* value and **keeps** the suggestion, so its source stays visible.
|
|
3808
|
+
*
|
|
3809
|
+
* No-op when the field has no suggestion.
|
|
3810
|
+
*
|
|
3811
|
+
* @param fieldId - Numeric id of the field.
|
|
3812
|
+
* @returns `this` for chaining.
|
|
3813
|
+
* @throws If `fieldId` is unknown or references a section.
|
|
3814
|
+
*/
|
|
3815
|
+
acceptSuggestion(fieldId) {
|
|
3816
|
+
this.assertField(fieldId);
|
|
3817
|
+
this.doc = applySuggestion(this.doc, fieldId);
|
|
3818
|
+
return this;
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Rejects a field's suggestion: removes it entirely, leaving the field's
|
|
3822
|
+
* value untouched.
|
|
3823
|
+
*
|
|
3824
|
+
* No-op when the field has no suggestion.
|
|
3825
|
+
*
|
|
3826
|
+
* @param fieldId - Numeric id of the field.
|
|
3827
|
+
* @returns `this` for chaining.
|
|
3828
|
+
*/
|
|
3829
|
+
rejectSuggestion(fieldId) {
|
|
3830
|
+
this.doc = removeSuggestion(this.doc, fieldId);
|
|
3831
|
+
return this;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
2234
3834
|
* Sets the `submittedAt` timestamp on the document.
|
|
2235
3835
|
*
|
|
2236
3836
|
* @param submittedAt - ISO 8601 timestamp string.
|
|
@@ -2286,6 +3886,7 @@ var FormValuesEditor = class {
|
|
|
2286
3886
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2287
3887
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2288
3888
|
if (entry.type === "section") throw new Error(`Item ${fieldId} is a section, not a field`);
|
|
3889
|
+
if (entry.type === "static") throw new Error(`Item ${fieldId} is a static field and holds no value`);
|
|
2289
3890
|
}
|
|
2290
3891
|
/**
|
|
2291
3892
|
* Asserts that `fieldId` is an array field and returns the current array value.
|
|
@@ -2294,7 +3895,7 @@ var FormValuesEditor = class {
|
|
|
2294
3895
|
assertArray(fieldId) {
|
|
2295
3896
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2296
3897
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2297
|
-
if (entry.type !== "array") throw new Error(`Field ${fieldId} is not an array field`);
|
|
3898
|
+
if (entry.type !== "array" && entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array field`);
|
|
2298
3899
|
const val = this.doc.values[String(fieldId)];
|
|
2299
3900
|
if (!Array.isArray(val)) throw new Error(`Field ${fieldId} does not currently hold an array value`);
|
|
2300
3901
|
return val;
|
|
@@ -2305,7 +3906,7 @@ var FormValuesEditor = class {
|
|
|
2305
3906
|
getOrInitArray(fieldId) {
|
|
2306
3907
|
const entry = this.engine.getFieldDef(fieldId);
|
|
2307
3908
|
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
2308
|
-
if (entry.type !== "array") throw new Error(`Field ${fieldId} is not an array field`);
|
|
3909
|
+
if (entry.type !== "array" && entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array field`);
|
|
2309
3910
|
const key = String(fieldId);
|
|
2310
3911
|
let val = this.doc.values[key];
|
|
2311
3912
|
if (!Array.isArray(val)) {
|
|
@@ -2314,8 +3915,28 @@ var FormValuesEditor = class {
|
|
|
2314
3915
|
}
|
|
2315
3916
|
return val;
|
|
2316
3917
|
}
|
|
3918
|
+
/**
|
|
3919
|
+
* Asserts that `fieldId` is an `array_obj` field holding a row at `index`
|
|
3920
|
+
* with a writable sub-field `subFieldId`, and returns that row.
|
|
3921
|
+
* The row is created in place when it is absent or not an object.
|
|
3922
|
+
*/
|
|
3923
|
+
assertArrayObjRow(fieldId, index, subFieldId) {
|
|
3924
|
+
const entry = this.engine.getFieldDef(fieldId);
|
|
3925
|
+
if (!entry) throw new Error(`Field with id ${fieldId} not found`);
|
|
3926
|
+
if (entry.type !== "array_obj") throw new Error(`Field ${fieldId} is not an array_obj field`);
|
|
3927
|
+
const subField = (entry.items ?? []).find((f) => f.id === subFieldId);
|
|
3928
|
+
if (!subField) throw new Error(`Sub-field ${subFieldId} does not belong to field ${fieldId}`);
|
|
3929
|
+
if (subField.type === "static") throw new Error(`Sub-field ${subFieldId} is a static field and holds no value`);
|
|
3930
|
+
const arr = this.assertArray(fieldId);
|
|
3931
|
+
if (index < 0 || index >= arr.length) throw new Error(`Index ${index} is out of bounds for array field ${fieldId} (length ${arr.length})`);
|
|
3932
|
+
const row = arr[index];
|
|
3933
|
+
if (typeof row === "object" && row !== null && !Array.isArray(row)) return row;
|
|
3934
|
+
const created = {};
|
|
3935
|
+
arr[index] = created;
|
|
3936
|
+
return created;
|
|
3937
|
+
}
|
|
2317
3938
|
};
|
|
2318
3939
|
//#endregion
|
|
2319
|
-
export { ConditionEvaluator, DependencyGraph, DocumentError, FieldValidator, FormDefinitionEditor, FormDefinitionValidator, FormEngine, FormValuesEditor, VisibilityResolver };
|
|
3940
|
+
export { ConditionEvaluator, DependencyGraph, DocumentError, FieldValidator, FormDefinitionEditor, FormDefinitionValidator, FormEngine, FormValuesEditor, VisibilityResolver, applySuggestion, getSuggestion, getSuggestions, removeSuggestion, setSuggestion };
|
|
2320
3941
|
|
|
2321
3942
|
//# sourceMappingURL=index.mjs.map
|