@bluprynt/forms-core 4.0.1 → 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.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
- file: new FileValidator()
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 select options for a `select` field.
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,19 +1799,335 @@ 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
- "arrayItem": { "oneOf": [
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": {
1810
+ "type": "object",
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": {
1335
2131
  "type": "object",
1336
2132
  "required": [
1337
2133
  "id",
@@ -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/fileField" }
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",
@@ -1543,17 +2499,44 @@ const itemRequiredProperties = new Map([
1543
2499
  "label",
1544
2500
  "options"
1545
2501
  ])],
2502
+ ["multiselect", new Set([
2503
+ "id",
2504
+ "type",
2505
+ "label",
2506
+ "options"
2507
+ ])],
1546
2508
  ["array", new Set([
1547
2509
  "id",
1548
2510
  "type",
1549
2511
  "label",
1550
2512
  "item"
1551
2513
  ])],
2514
+ ["array_obj", new Set([
2515
+ "id",
2516
+ "type",
2517
+ "label",
2518
+ "items"
2519
+ ])],
1552
2520
  ["file", new Set([
1553
2521
  "id",
1554
2522
  "type",
1555
2523
  "label"
1556
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
+ ])],
1557
2540
  ["section", new Set([
1558
2541
  "id",
1559
2542
  "type",
@@ -1567,6 +2550,9 @@ const itemAllowedProperties = new Map([
1567
2550
  "type",
1568
2551
  "label",
1569
2552
  "description",
2553
+ "placeholder",
2554
+ "answer_guidelines",
2555
+ "reference_id",
1570
2556
  "condition",
1571
2557
  "validation"
1572
2558
  ])],
@@ -1575,6 +2561,9 @@ const itemAllowedProperties = new Map([
1575
2561
  "type",
1576
2562
  "label",
1577
2563
  "description",
2564
+ "placeholder",
2565
+ "answer_guidelines",
2566
+ "reference_id",
1578
2567
  "condition",
1579
2568
  "validation"
1580
2569
  ])],
@@ -1583,6 +2572,9 @@ const itemAllowedProperties = new Map([
1583
2572
  "type",
1584
2573
  "label",
1585
2574
  "description",
2575
+ "placeholder",
2576
+ "answer_guidelines",
2577
+ "reference_id",
1586
2578
  "condition",
1587
2579
  "validation"
1588
2580
  ])],
@@ -1591,6 +2583,9 @@ const itemAllowedProperties = new Map([
1591
2583
  "type",
1592
2584
  "label",
1593
2585
  "description",
2586
+ "placeholder",
2587
+ "answer_guidelines",
2588
+ "reference_id",
1594
2589
  "condition",
1595
2590
  "validation"
1596
2591
  ])],
@@ -1599,6 +2594,21 @@ const itemAllowedProperties = new Map([
1599
2594
  "type",
1600
2595
  "label",
1601
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",
1602
2612
  "condition",
1603
2613
  "options",
1604
2614
  "validation"
@@ -1608,23 +2618,78 @@ const itemAllowedProperties = new Map([
1608
2618
  "type",
1609
2619
  "label",
1610
2620
  "description",
2621
+ "placeholder",
2622
+ "answer_guidelines",
2623
+ "reference_id",
1611
2624
  "condition",
1612
2625
  "item",
1613
2626
  "validation"
1614
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
+ ])],
1615
2641
  ["file", new Set([
1616
2642
  "id",
1617
2643
  "type",
1618
2644
  "label",
1619
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",
1620
2671
  "condition",
1621
2672
  "validation"
1622
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
+ ])],
1623
2685
  ["section", new Set([
1624
2686
  "id",
1625
2687
  "type",
1626
2688
  "title",
1627
2689
  "description",
2690
+ "placeholder",
2691
+ "answer_guidelines",
2692
+ "reference_id",
1628
2693
  "condition",
1629
2694
  "content"
1630
2695
  ])]
@@ -1642,12 +2707,17 @@ const itemAllowedProperties = new Map([
1642
2707
  * ### Semantic validation (`validate`)
1643
2708
  * Checks for logical issues that go beyond JSON schema validity:
1644
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.
1645
2711
  * 2. **Nesting depth** (`NESTING_DEPTH`) -- sections may not be nested more
1646
2712
  * than 3 levels deep.
1647
2713
  * 3. **Unknown field references** (`UNKNOWN_FIELD_REF`) -- conditions must
1648
2714
  * only reference field ids that exist in the registry.
1649
- * 4. **Condition references section** (`CONDITION_REFS_SECTION`) -- conditions
1650
- * must not reference section ids, because sections have no values.
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.
1651
2721
  * 5. **Constraint contradictions** (`INVALID_MIN_MAX`) -- e.g. `minLength > maxLength`,
1652
2722
  * `min > max`, `minDate > maxDate` (absolute dates only), `minItems > maxItems`.
1653
2723
  * 6. **Invalid regex** (`INVALID_REGEX`) -- string field `pattern` values must
@@ -1845,22 +2915,51 @@ var FormDefinitionValidator = class {
1845
2915
  const issues = [];
1846
2916
  this.checkDuplicateIds(definition.content, issues);
1847
2917
  this.checkNestingDepth(definition.content, 0, issues);
1848
- this.checkConditionRefs(registry, issues);
1849
- this.checkConditionRefsSection(registry, issues);
2918
+ const arrayObjItemIds = this.collectArrayObjItemIds(definition.content);
2919
+ this.checkConditionRefsArrayObjItem(registry, arrayObjItemIds, issues);
2920
+ this.checkConditionRefs(registry, arrayObjItemIds, issues);
2921
+ this.checkConditionRefsValueless(registry, issues);
1850
2922
  this.checkConstraintContradictions(registry, issues);
1851
2923
  this.checkInvalidRegex(registry, issues);
1852
2924
  return issues;
1853
2925
  }
1854
2926
  checkDuplicateIds(content, issues) {
1855
2927
  const seen = /* @__PURE__ */ new Set();
1856
- this.walkItems(content, (item) => {
1857
- if (seen.has(item.id)) issues.push({
2928
+ const record = (id) => {
2929
+ if (seen.has(id)) issues.push({
1858
2930
  code: "DUPLICATE_ID",
1859
- message: `Duplicate id: ${item.id}`,
1860
- itemId: item.id
2931
+ message: `Duplicate id: ${id}`,
2932
+ itemId: id
1861
2933
  });
1862
- else seen.add(item.id);
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);
2939
+ });
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);
1863
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
+ }
1864
2963
  }
1865
2964
  checkNestingDepth(content, depth, issues) {
1866
2965
  for (const item of content) if (item.type === "section") if (depth >= 3) issues.push({
@@ -1870,28 +2969,37 @@ var FormDefinitionValidator = class {
1870
2969
  });
1871
2970
  else this.checkNestingDepth(item.content, depth + 1, issues);
1872
2971
  }
1873
- checkConditionRefs(registry, issues) {
2972
+ checkConditionRefs(registry, arrayObjItemIds, issues) {
1874
2973
  for (const [id, entry] of registry) {
1875
2974
  if (!entry.condition) continue;
1876
2975
  const refs = DependencyGraph.extractFieldRefs(entry.condition);
1877
- for (const ref of refs) if (!registry.has(ref)) issues.push({
1878
- code: "UNKNOWN_FIELD_REF",
1879
- message: `Condition references unknown field: ${ref} (in item ${id})`,
1880
- itemId: id
1881
- });
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
+ }
1882
2984
  }
1883
2985
  }
1884
- checkConditionRefsSection(registry, issues) {
2986
+ checkConditionRefsValueless(registry, issues) {
1885
2987
  for (const [id, entry] of registry) {
1886
2988
  if (!entry.condition) continue;
1887
2989
  const refs = DependencyGraph.extractFieldRefs(entry.condition);
1888
2990
  for (const ref of refs) {
1889
2991
  const refEntry = registry.get(ref);
1890
- if (refEntry && refEntry.type === "section") issues.push({
2992
+ if (!refEntry) continue;
2993
+ if (refEntry.type === "section") issues.push({
1891
2994
  code: "CONDITION_REFS_SECTION",
1892
2995
  message: `Condition references section ${ref}, which has no value (in item ${id})`,
1893
2996
  itemId: id
1894
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
+ });
1895
3003
  }
1896
3004
  }
1897
3005
  }
@@ -1932,7 +3040,8 @@ var FormDefinitionValidator = class {
1932
3040
  }
1933
3041
  break;
1934
3042
  }
1935
- case "array": {
3043
+ case "array":
3044
+ case "array_obj": {
1936
3045
  const v = entry.validation;
1937
3046
  if (v.minItems !== void 0 && v.maxItems !== void 0 && v.maxItems < v.minItems) issues.push({
1938
3047
  code: "INVALID_MIN_MAX",
@@ -2018,6 +3127,7 @@ var VisibilityResolver = class {
2018
3127
  registry;
2019
3128
  conditionEvaluator;
2020
3129
  topologicalOrder;
3130
+ fieldTypes;
2021
3131
  /**
2022
3132
  * @param registry - The engine's field registry.
2023
3133
  * @param conditionEvaluator - Evaluator for condition trees.
@@ -2027,6 +3137,7 @@ var VisibilityResolver = class {
2027
3137
  this.registry = registry;
2028
3138
  this.conditionEvaluator = conditionEvaluator;
2029
3139
  this.topologicalOrder = topologicalOrder;
3140
+ this.fieldTypes = new Map([...registry].map(([id, entry]) => [id, entry.type]));
2030
3141
  }
2031
3142
  /**
2032
3143
  * Determines whether a single field or section is visible.
@@ -2053,7 +3164,8 @@ var VisibilityResolver = class {
2053
3164
  if (entry.condition) {
2054
3165
  if (!this.conditionEvaluator.evalCondition(entry.condition, {
2055
3166
  values,
2056
- now
3167
+ now,
3168
+ fieldTypes: this.fieldTypes
2057
3169
  })) return false;
2058
3170
  }
2059
3171
  if (entry.parentId !== void 0) return this.isVisible(entry.parentId, values, now);
@@ -2091,7 +3203,8 @@ var VisibilityResolver = class {
2091
3203
  const visible = this.conditionEvaluator.evalCondition(entry.condition, {
2092
3204
  values,
2093
3205
  visibilityMap: result,
2094
- now
3206
+ now,
3207
+ fieldTypes: this.fieldTypes
2095
3208
  });
2096
3209
  result.set(id, visible);
2097
3210
  } else result.set(id, true);
@@ -2358,14 +3471,16 @@ var FormEngine = class FormEngine {
2358
3471
  }
2359
3472
  static walkContent(content, parentId, registry, contentOrder) {
2360
3473
  for (const item of content) {
3474
+ const isValueField = item.type !== "section" && item.type !== "static";
2361
3475
  const entry = {
2362
3476
  id: item.id,
2363
3477
  type: item.type,
2364
3478
  condition: item.condition,
2365
- validation: item.type !== "section" ? item.validation : void 0,
3479
+ validation: isValueField ? item.validation : void 0,
2366
3480
  parentId,
2367
- options: item.type === "select" ? item.options : void 0,
3481
+ options: item.type === "select" || item.type === "multiselect" ? item.options : void 0,
2368
3482
  item: item.type === "array" ? item.item : void 0,
3483
+ items: item.type === "array_obj" ? item.items : void 0,
2369
3484
  label: item.type !== "section" ? item.label : void 0,
2370
3485
  title: item.type === "section" ? item.title : void 0
2371
3486
  };
@@ -2376,6 +3491,108 @@ var FormEngine = class FormEngine {
2376
3491
  }
2377
3492
  };
2378
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
2379
3596
  //#region src/form-values-editor.ts
2380
3597
  /**
2381
3598
  * Mutable editor for building and modifying form values against a {@link FormDefinition}.
@@ -2447,13 +3664,16 @@ var FormValuesEditor = class {
2447
3664
  * If the field currently has no value, it is initialized to an empty array
2448
3665
  * before appending.
2449
3666
  *
2450
- * @param fieldId - Numeric id of the array field.
2451
- * @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.
2452
3670
  * @returns `this` for chaining.
2453
3671
  * @throws If `fieldId` is not an array field.
2454
3672
  */
2455
3673
  addArrayItem(fieldId, value) {
2456
- this.getOrInitArray(fieldId).push(value);
3674
+ const arr = this.getOrInitArray(fieldId);
3675
+ const entry = this.engine.getFieldDef(fieldId);
3676
+ arr.push(value === void 0 && entry?.type === "array_obj" ? {} : value);
2457
3677
  return this;
2458
3678
  }
2459
3679
  /**
@@ -2503,6 +3723,114 @@ var FormValuesEditor = class {
2503
3723
  return this;
2504
3724
  }
2505
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
+ /**
2506
3834
  * Sets the `submittedAt` timestamp on the document.
2507
3835
  *
2508
3836
  * @param submittedAt - ISO 8601 timestamp string.
@@ -2558,6 +3886,7 @@ var FormValuesEditor = class {
2558
3886
  const entry = this.engine.getFieldDef(fieldId);
2559
3887
  if (!entry) throw new Error(`Field with id ${fieldId} not found`);
2560
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`);
2561
3890
  }
2562
3891
  /**
2563
3892
  * Asserts that `fieldId` is an array field and returns the current array value.
@@ -2566,7 +3895,7 @@ var FormValuesEditor = class {
2566
3895
  assertArray(fieldId) {
2567
3896
  const entry = this.engine.getFieldDef(fieldId);
2568
3897
  if (!entry) throw new Error(`Field with id ${fieldId} not found`);
2569
- 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`);
2570
3899
  const val = this.doc.values[String(fieldId)];
2571
3900
  if (!Array.isArray(val)) throw new Error(`Field ${fieldId} does not currently hold an array value`);
2572
3901
  return val;
@@ -2577,7 +3906,7 @@ var FormValuesEditor = class {
2577
3906
  getOrInitArray(fieldId) {
2578
3907
  const entry = this.engine.getFieldDef(fieldId);
2579
3908
  if (!entry) throw new Error(`Field with id ${fieldId} not found`);
2580
- 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`);
2581
3910
  const key = String(fieldId);
2582
3911
  let val = this.doc.values[key];
2583
3912
  if (!Array.isArray(val)) {
@@ -2586,8 +3915,28 @@ var FormValuesEditor = class {
2586
3915
  }
2587
3916
  return val;
2588
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
+ }
2589
3938
  };
2590
3939
  //#endregion
2591
- 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 };
2592
3941
 
2593
3942
  //# sourceMappingURL=index.mjs.map