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