@marktiderman/genesis-forms 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -10
- package/dist/destinations.cjs +202 -39
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +67 -21
- package/dist/destinations.d.ts +67 -21
- package/dist/destinations.js +202 -40
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-Bj23YJlB.d.cts → engine-BaHPC2-1.d.cts} +2 -2
- package/dist/{engine-CzinTJph.d.ts → engine-Da99Tv-E.d.ts} +2 -2
- package/dist/index.cjs +996 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +288 -11
- package/dist/index.d.ts +288 -11
- package/dist/index.js +975 -168
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +563 -173
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -474
- package/dist/server.d.ts +3 -474
- package/dist/server.js +558 -174
- package/dist/server.js.map +1 -1
- package/dist/submit-protocol-CFi-0Qd0.d.ts +840 -0
- package/dist/submit-protocol-D0cOckmK.d.cts +840 -0
- package/dist/testing.cjs +250 -154
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -4
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +250 -154
- package/dist/testing.js.map +1 -1
- package/dist/{types-jqDKcmP4.d.cts → types-C0pUfew3.d.cts} +7 -1
- package/dist/{types-jqDKcmP4.d.ts → types-C0pUfew3.d.ts} +7 -1
- package/dist/{validate-DvfQHYnu.d.ts → validate-DpmBssFk.d.ts} +258 -140
- package/dist/{validate-irIPubxc.d.cts → validate-U5LoW3eR.d.cts} +258 -140
- package/package.json +2 -2
- package/dist/upload-protocol-BOe018kL.d.cts +0 -122
- package/dist/upload-protocol-BUO74WkS.d.ts +0 -122
package/dist/testing.cjs
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/props-check.ts
|
|
4
|
+
var RESERVED_NAMES = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
5
|
+
var isPlainRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
6
|
+
var isWholeNumber = (v) => typeof v === "number" && Number.isInteger(v);
|
|
7
|
+
var isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
8
|
+
function propsChecker(prefix, report) {
|
|
9
|
+
const at = (path) => path ? `${prefix}.${path}` : prefix;
|
|
10
|
+
const issue = (path, message) => report(at(path), message);
|
|
11
|
+
return {
|
|
12
|
+
issue,
|
|
13
|
+
text(v, path, nonEmpty = false) {
|
|
14
|
+
if (typeof v !== "string" || nonEmpty && !v) issue(path, nonEmpty ? "Required." : "Required text.");
|
|
15
|
+
},
|
|
16
|
+
optionalText(v, path) {
|
|
17
|
+
if (v !== void 0 && typeof v !== "string") issue(path, "Must be text.");
|
|
18
|
+
},
|
|
19
|
+
optionalBoolean(v, path) {
|
|
20
|
+
if (v !== void 0 && typeof v !== "boolean") issue(path, "Must be true or false.");
|
|
21
|
+
},
|
|
22
|
+
optionalInt(v, path, floor) {
|
|
23
|
+
if (v !== void 0 && !(isWholeNumber(v) && v >= floor)) issue(path, `Must be a whole number of ${floor} or more.`);
|
|
24
|
+
},
|
|
25
|
+
ordered(low, high, path, what) {
|
|
26
|
+
if (isWholeNumber(low) && isWholeNumber(high) && low > high) issue(path, `Can't be more than the ${what}.`);
|
|
27
|
+
},
|
|
28
|
+
name(v, path, what) {
|
|
29
|
+
if (typeof v !== "string" || !v) {
|
|
30
|
+
issue(path, `${what} must be a non-empty string.`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (RESERVED_NAMES.has(v)) {
|
|
34
|
+
issue(path, `"${v}" is reserved and cannot be used as ${what.toLowerCase()}.`);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
3
42
|
// src/formula.ts
|
|
4
43
|
var FORMULA_FUNCTIONS = ["sum", "avg", "min", "max"];
|
|
5
44
|
var MAX_FORMULA_LENGTH = 2e3;
|
|
@@ -534,6 +573,17 @@ var calculatedFieldType = {
|
|
|
534
573
|
value: { json: true },
|
|
535
574
|
operators: numberOps,
|
|
536
575
|
a11y: { semantics: "control" },
|
|
576
|
+
// A number, unless the format says text; without a format it can hold either, so it isn't known.
|
|
577
|
+
literal: (props) => props.format === "text" ? "string" : props.format === "number" || props.format === "percent" ? "number" : void 0,
|
|
578
|
+
// The formula itself is checked once every key is known (`parseForm`).
|
|
579
|
+
checkProps: (props, check) => {
|
|
580
|
+
if (props.format !== void 0 && props.format !== "number" && props.format !== "percent" && props.format !== "text") {
|
|
581
|
+
check.issue("format", "Must be number, percent or text.");
|
|
582
|
+
}
|
|
583
|
+
if (props.decimals !== void 0 && !(isWholeNumber(props.decimals) && props.decimals >= 0 && props.decimals <= MAX_CALCULATED_DECIMALS)) {
|
|
584
|
+
check.issue("decimals", `Must be a whole number from 0 to ${MAX_CALCULATED_DECIMALS}.`);
|
|
585
|
+
}
|
|
586
|
+
},
|
|
537
587
|
// The engine works the value out; any number, text or true / false it made is valid.
|
|
538
588
|
validate: (value) => typeof value === "number" || typeof value === "string" || typeof value === "boolean" ? null : "This value is worked out from other answers.",
|
|
539
589
|
summarize: (value, field) => formatCalculated(value, field)
|
|
@@ -591,12 +641,42 @@ var MAX_HIGHLIGHT_NOTE_LENGTH = 1e3;
|
|
|
591
641
|
var MAX_HIGHLIGHT_QUOTE_LENGTH = 2e3;
|
|
592
642
|
var HIGHLIGHT_NOTE_MESSAGE = "Add a note to each highlight.";
|
|
593
643
|
var sourceText = (field) => typeof field.props?.text === "string" && field.props.text ? field.props.text : void 0;
|
|
644
|
+
var listProps = (props, check) => {
|
|
645
|
+
check.optionalInt(props.min, "min", 0);
|
|
646
|
+
check.optionalInt(props.max, "max", 1);
|
|
647
|
+
check.optionalInt(props.itemMaxLength, "itemMaxLength", 1);
|
|
648
|
+
check.optionalText(props.placeholder, "placeholder");
|
|
649
|
+
check.ordered(props.min, props.max ?? 10, "min", "most items");
|
|
650
|
+
};
|
|
651
|
+
var tableProps = (props, check) => {
|
|
652
|
+
const columns = props.columns;
|
|
653
|
+
if (!Array.isArray(columns) || columns.length < 1 || columns.length > MAX_TABLE_COLUMNS) {
|
|
654
|
+
check.issue("columns", `Must be a list of 1 to ${MAX_TABLE_COLUMNS} columns.`);
|
|
655
|
+
} else {
|
|
656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
657
|
+
columns.forEach((c, j) => {
|
|
658
|
+
const at = `columns[${j}]`;
|
|
659
|
+
if (!isPlainRecord(c)) return check.issue(at, "Each column needs a key and a label.");
|
|
660
|
+
if (check.name(c.key, `${at}.key`, "A column key")) {
|
|
661
|
+
if (seen.has(c.key)) check.issue(`${at}.key`, `Duplicate column key "${c.key}".`);
|
|
662
|
+
seen.add(c.key);
|
|
663
|
+
}
|
|
664
|
+
check.text(c.label, `${at}.label`);
|
|
665
|
+
check.optionalBoolean(c.long, `${at}.long`);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
check.optionalInt(props.minRows, "minRows", 0);
|
|
669
|
+
check.optionalInt(props.maxRows, "maxRows", 1);
|
|
670
|
+
check.ordered(props.minRows, props.maxRows ?? 3, "minRows", "most rows");
|
|
671
|
+
};
|
|
594
672
|
var structuredFieldTypes = [
|
|
595
673
|
{
|
|
596
674
|
type: "list",
|
|
597
675
|
label: "List",
|
|
598
676
|
category: "text",
|
|
599
677
|
value: { json: true },
|
|
678
|
+
literal: "string",
|
|
679
|
+
checkProps: listProps,
|
|
600
680
|
// logic.ts `contains` on an array is exact item equality, same as multi-choice, so it is offered.
|
|
601
681
|
operators: ["contains", "answered", "empty"],
|
|
602
682
|
a11y: { semantics: "group" },
|
|
@@ -622,6 +702,7 @@ var structuredFieldTypes = [
|
|
|
622
702
|
label: "Table",
|
|
623
703
|
category: "advanced",
|
|
624
704
|
value: { json: true },
|
|
705
|
+
checkProps: tableProps,
|
|
625
706
|
operators: ["answered", "empty"],
|
|
626
707
|
a11y: { semantics: "group" },
|
|
627
708
|
isEmpty: (value) => emptyList(value, (r) => isObject(r) && Object.values(r).every(blankText)),
|
|
@@ -665,6 +746,10 @@ var structuredFieldTypes = [
|
|
|
665
746
|
label: "Notes at moments",
|
|
666
747
|
category: "media",
|
|
667
748
|
value: { json: true },
|
|
749
|
+
checkProps: (props, check) => {
|
|
750
|
+
check.optionalInt(props.maxNotes, "maxNotes", 1);
|
|
751
|
+
check.optionalInt(props.textMaxLength, "textMaxLength", 1);
|
|
752
|
+
},
|
|
668
753
|
operators: ["answered", "empty"],
|
|
669
754
|
a11y: { semantics: "group" },
|
|
670
755
|
isEmpty: (value) => emptyList(value, (n) => isObject(n) && blankText(n.text)),
|
|
@@ -688,6 +773,10 @@ var structuredFieldTypes = [
|
|
|
688
773
|
label: "Highlights",
|
|
689
774
|
category: "media",
|
|
690
775
|
value: { json: true },
|
|
776
|
+
checkProps: (props, check) => {
|
|
777
|
+
check.optionalText(props.text, "text");
|
|
778
|
+
check.optionalInt(props.maxHighlights, "maxHighlights", 1);
|
|
779
|
+
},
|
|
691
780
|
operators: ["answered", "empty"],
|
|
692
781
|
a11y: { semantics: "group" },
|
|
693
782
|
isEmpty: (value) => emptyList(value, () => false),
|
|
@@ -779,6 +868,22 @@ var photoFieldType = {
|
|
|
779
868
|
value: { json: true },
|
|
780
869
|
operators: ["answered", "empty"],
|
|
781
870
|
a11y: { semantics: "group" },
|
|
871
|
+
checkProps: (props, check) => {
|
|
872
|
+
if (props.maxFiles !== void 0 && !(isWholeNumber(props.maxFiles) && props.maxFiles >= 1 && props.maxFiles <= MAX_PHOTO_FILES)) {
|
|
873
|
+
check.issue("maxFiles", `Must be a whole number from 1 to ${MAX_PHOTO_FILES}.`);
|
|
874
|
+
}
|
|
875
|
+
if (props.maxSizeMb !== void 0 && !(isWholeNumber(props.maxSizeMb) && props.maxSizeMb >= 1 && props.maxSizeMb <= MAX_PHOTO_SIZE_MB)) {
|
|
876
|
+
check.issue("maxSizeMb", `Must be a whole number from 1 to ${MAX_PHOTO_SIZE_MB}.`);
|
|
877
|
+
}
|
|
878
|
+
if (props.formats !== void 0) {
|
|
879
|
+
const formats = props.formats;
|
|
880
|
+
if (!Array.isArray(formats) || formats.length === 0 || !formats.every(isPhotoFormat)) {
|
|
881
|
+
check.issue("formats", "Must be a list of one or more of jpeg, png, heic and webp.");
|
|
882
|
+
} else if (new Set(formats).size !== formats.length) {
|
|
883
|
+
check.issue("formats", "Name each format once.");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
},
|
|
782
887
|
isEmpty: (value) => value === void 0 || value === null || Array.isArray(value) && value.length === 0,
|
|
783
888
|
validate: (value, field) => {
|
|
784
889
|
if (!Array.isArray(value) || !value.every(isPhotoRef)) return "Add a photo.";
|
|
@@ -796,22 +901,59 @@ var photoFieldType = {
|
|
|
796
901
|
};
|
|
797
902
|
|
|
798
903
|
// src/fields.ts
|
|
799
|
-
var
|
|
904
|
+
var VALIDATOR_FAILED_MESSAGE = "This answer couldn't be checked.";
|
|
905
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/field-types@2");
|
|
906
|
+
var BUILT_IN = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/built-in-field-types@2");
|
|
800
907
|
var globalScope = globalThis;
|
|
801
908
|
var registry = globalScope[REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
);
|
|
909
|
+
var builtIn = globalScope[BUILT_IN] ??= /* @__PURE__ */ new Set();
|
|
910
|
+
var freezeRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.freeze({ ...v }) : v;
|
|
911
|
+
function frozen(manifest) {
|
|
912
|
+
const copy = { ...manifest };
|
|
913
|
+
for (const key of ["choice", "a11y", "value"]) if (key in copy) copy[key] = freezeRecord(copy[key]);
|
|
914
|
+
if (Array.isArray(copy.operators)) {
|
|
915
|
+
const operators = Object.freeze([...copy.operators]);
|
|
916
|
+
Object.defineProperty(copy, "operators", { get: () => [...operators], enumerable: true });
|
|
809
917
|
}
|
|
810
|
-
|
|
918
|
+
return Object.freeze(copy);
|
|
919
|
+
}
|
|
920
|
+
function registerBuiltIn(manifest) {
|
|
921
|
+
builtIn.add(manifest.type);
|
|
922
|
+
if (!registry.has(manifest.type)) registry.set(manifest.type, frozen(manifest));
|
|
811
923
|
}
|
|
812
924
|
function getFieldType(type) {
|
|
813
925
|
return registry.get(type);
|
|
814
926
|
}
|
|
927
|
+
function choiceKind(type) {
|
|
928
|
+
return registry.get(type)?.choice;
|
|
929
|
+
}
|
|
930
|
+
function isChoiceField(block) {
|
|
931
|
+
return !!block && block.kind === "field" && typeof block.type === "string" && choiceKind(block.type) !== void 0;
|
|
932
|
+
}
|
|
933
|
+
function literalType(type, props) {
|
|
934
|
+
const literal = registry.get(type)?.literal;
|
|
935
|
+
if (typeof literal !== "function") return literal;
|
|
936
|
+
try {
|
|
937
|
+
return literal(props);
|
|
938
|
+
} catch {
|
|
939
|
+
return void 0;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
var CONTENT_REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/content-types@1");
|
|
943
|
+
var contentScope = globalThis;
|
|
944
|
+
var contentRegistry = contentScope[CONTENT_REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
945
|
+
function getContentType(type) {
|
|
946
|
+
return contentRegistry.get(type);
|
|
947
|
+
}
|
|
948
|
+
function listContentTypes() {
|
|
949
|
+
return [...contentRegistry.values()];
|
|
950
|
+
}
|
|
951
|
+
for (const manifest of [
|
|
952
|
+
{ type: "heading", label: "Heading" },
|
|
953
|
+
{ type: "text", label: "Text" }
|
|
954
|
+
]) {
|
|
955
|
+
if (!contentRegistry.has(manifest.type)) contentRegistry.set(manifest.type, frozen(manifest));
|
|
956
|
+
}
|
|
815
957
|
function isEmptyValue(value, field) {
|
|
816
958
|
const manifest = field ? registry.get(field.type) : void 0;
|
|
817
959
|
if (manifest?.isEmpty) return manifest.isEmpty(value);
|
|
@@ -994,6 +1136,18 @@ function isPhoneNumber(value) {
|
|
|
994
1136
|
}
|
|
995
1137
|
return digits >= 7 && digits <= 15;
|
|
996
1138
|
}
|
|
1139
|
+
var oneOptionValidate = (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.";
|
|
1140
|
+
var oneOptionSummary = (value, field) => optionLabel(field, String(value));
|
|
1141
|
+
var optionsValidate = (value, field) => {
|
|
1142
|
+
const allowed = new Set(choiceOptions(field).map((o) => o.value));
|
|
1143
|
+
const others = Array.isArray(value) ? value.filter((v) => typeof v === "string" && !allowed.has(v)) : [];
|
|
1144
|
+
return Array.isArray(value) && new Set(value).size === value.length && value.every((v) => typeof v === "string") && (others.length === 0 || others.length === 1 && typedOther(field, others[0])) ? null : "Choose from the options.";
|
|
1145
|
+
};
|
|
1146
|
+
var optionsSummary = (value, field) => Array.isArray(value) ? value.map((v) => optionLabel(field, String(v))).join(", ") : String(value);
|
|
1147
|
+
var scaleProps = (props, check) => {
|
|
1148
|
+
check.optionalText(props.lowLabel, "lowLabel");
|
|
1149
|
+
check.optionalText(props.highLabel, "highLabel");
|
|
1150
|
+
};
|
|
997
1151
|
var builtins = [
|
|
998
1152
|
{
|
|
999
1153
|
type: "short-text",
|
|
@@ -1002,6 +1156,7 @@ var builtins = [
|
|
|
1002
1156
|
value: { json: true },
|
|
1003
1157
|
operators: textOps,
|
|
1004
1158
|
a11y: { semantics: "control" },
|
|
1159
|
+
literal: "string",
|
|
1005
1160
|
validate: textValidate(500)
|
|
1006
1161
|
},
|
|
1007
1162
|
{
|
|
@@ -1011,6 +1166,7 @@ var builtins = [
|
|
|
1011
1166
|
value: { json: true },
|
|
1012
1167
|
operators: textOps,
|
|
1013
1168
|
a11y: { semantics: "control" },
|
|
1169
|
+
literal: "string",
|
|
1014
1170
|
validate: textValidate(5e3)
|
|
1015
1171
|
},
|
|
1016
1172
|
{
|
|
@@ -1020,6 +1176,7 @@ var builtins = [
|
|
|
1020
1176
|
value: { json: true },
|
|
1021
1177
|
operators: textOps,
|
|
1022
1178
|
a11y: { semantics: "control", autocomplete: "email", inputMode: "email" },
|
|
1179
|
+
literal: "string",
|
|
1023
1180
|
validate: (value) => typeof value === "string" && isEmailAddress(value.trim()) ? null : "Enter an email address like name@example.com.",
|
|
1024
1181
|
normalize: (value) => typeof value === "string" ? value.trim() : value
|
|
1025
1182
|
},
|
|
@@ -1030,6 +1187,7 @@ var builtins = [
|
|
|
1030
1187
|
value: { json: true },
|
|
1031
1188
|
operators: ["eq", "neq", "answered", "empty"],
|
|
1032
1189
|
a11y: { semantics: "control", autocomplete: "tel", inputMode: "tel" },
|
|
1190
|
+
literal: "string",
|
|
1033
1191
|
validate: (value) => typeof value === "string" && isPhoneNumber(value.trim()) ? null : "Enter a phone number with 7 to 15 digits.",
|
|
1034
1192
|
normalize: (value) => typeof value === "string" ? value.trim() : value,
|
|
1035
1193
|
summarize: (value) => String(value)
|
|
@@ -1041,6 +1199,13 @@ var builtins = [
|
|
|
1041
1199
|
value: { json: true },
|
|
1042
1200
|
operators: numberOps2,
|
|
1043
1201
|
a11y: { semantics: "group" },
|
|
1202
|
+
literal: "number",
|
|
1203
|
+
checkProps: (props, check) => {
|
|
1204
|
+
scaleProps(props, check);
|
|
1205
|
+
if (props.display !== void 0 && props.display !== "numbers" && props.display !== "stars") {
|
|
1206
|
+
check.issue("display", "Must be numbers or stars.");
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1044
1209
|
validate: (value, field) => {
|
|
1045
1210
|
const { min, max } = intRange(field, 1, 5);
|
|
1046
1211
|
return Number.isInteger(value) && value >= min && value <= max ? null : `Choose a rating from ${min} to ${max}.`;
|
|
@@ -1058,6 +1223,8 @@ var builtins = [
|
|
|
1058
1223
|
value: { json: true },
|
|
1059
1224
|
operators: numberOps2,
|
|
1060
1225
|
a11y: { semantics: "group" },
|
|
1226
|
+
literal: "number",
|
|
1227
|
+
checkProps: scaleProps,
|
|
1061
1228
|
validate: (value) => Number.isInteger(value) && value >= 0 && value <= 10 ? null : "Choose a score from 0 to 10.",
|
|
1062
1229
|
summarize: (value, field) => {
|
|
1063
1230
|
const end = scaleEndLabel(field, value, 0, 10);
|
|
@@ -1071,8 +1238,10 @@ var builtins = [
|
|
|
1071
1238
|
value: { json: true },
|
|
1072
1239
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
1073
1240
|
a11y: { semantics: "group" },
|
|
1074
|
-
|
|
1075
|
-
|
|
1241
|
+
literal: "string",
|
|
1242
|
+
choice: { multiple: false },
|
|
1243
|
+
validate: oneOptionValidate,
|
|
1244
|
+
summarize: oneOptionSummary
|
|
1076
1245
|
},
|
|
1077
1246
|
{
|
|
1078
1247
|
type: "multi-choice",
|
|
@@ -1081,12 +1250,10 @@ var builtins = [
|
|
|
1081
1250
|
value: { json: true },
|
|
1082
1251
|
operators: ["contains", "answered", "empty"],
|
|
1083
1252
|
a11y: { semantics: "group" },
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
},
|
|
1089
|
-
summarize: (value, field) => Array.isArray(value) ? value.map((v) => optionLabel(field, String(v))).join(", ") : String(value)
|
|
1253
|
+
literal: "string",
|
|
1254
|
+
choice: { multiple: true },
|
|
1255
|
+
validate: optionsValidate,
|
|
1256
|
+
summarize: optionsSummary
|
|
1090
1257
|
},
|
|
1091
1258
|
{
|
|
1092
1259
|
type: "yes-no",
|
|
@@ -1095,6 +1262,7 @@ var builtins = [
|
|
|
1095
1262
|
value: { json: true },
|
|
1096
1263
|
operators: ["eq", "neq", "answered", "empty"],
|
|
1097
1264
|
a11y: { semantics: "group" },
|
|
1265
|
+
literal: "boolean",
|
|
1098
1266
|
validate: (value) => typeof value === "boolean" ? null : "Choose yes or no.",
|
|
1099
1267
|
summarize: (value) => value ? "Yes" : "No"
|
|
1100
1268
|
},
|
|
@@ -1105,8 +1273,10 @@ var builtins = [
|
|
|
1105
1273
|
value: { json: true },
|
|
1106
1274
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
1107
1275
|
a11y: { semantics: "control" },
|
|
1108
|
-
|
|
1109
|
-
|
|
1276
|
+
literal: "string",
|
|
1277
|
+
choice: { multiple: false },
|
|
1278
|
+
validate: oneOptionValidate,
|
|
1279
|
+
summarize: oneOptionSummary
|
|
1110
1280
|
},
|
|
1111
1281
|
{
|
|
1112
1282
|
type: "number",
|
|
@@ -1115,6 +1285,16 @@ var builtins = [
|
|
|
1115
1285
|
value: { json: true },
|
|
1116
1286
|
operators: numberOps2,
|
|
1117
1287
|
a11y: { semantics: "control", inputMode: "decimal" },
|
|
1288
|
+
literal: "number",
|
|
1289
|
+
checkProps: (props, check) => {
|
|
1290
|
+
for (const k of ["min", "max", "step"]) {
|
|
1291
|
+
if (props[k] !== void 0 && !isFiniteNumber(props[k])) check.issue(k, "Must be a number.");
|
|
1292
|
+
}
|
|
1293
|
+
if (isFiniteNumber(props.step) && props.step <= 0) check.issue("step", "Must be above 0.");
|
|
1294
|
+
if (isFiniteNumber(props.min) && isFiniteNumber(props.max) && props.min > props.max) {
|
|
1295
|
+
check.issue("min", "Can't be more than the largest number.");
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1118
1298
|
validate: (value, field) => {
|
|
1119
1299
|
if (typeof value !== "number" || !Number.isFinite(value)) return "Enter a number.";
|
|
1120
1300
|
const { min, max, step } = numberLimits(field);
|
|
@@ -1141,6 +1321,17 @@ var builtins = [
|
|
|
1141
1321
|
value: { json: true },
|
|
1142
1322
|
operators: ["eq", "neq", "answered", "empty"],
|
|
1143
1323
|
a11y: { semantics: "control" },
|
|
1324
|
+
literal: "string",
|
|
1325
|
+
checkProps: (props, check) => {
|
|
1326
|
+
for (const k of ["min", "max"]) {
|
|
1327
|
+
if (props[k] !== void 0 && !(typeof props[k] === "string" && isIsoDate(props[k]))) {
|
|
1328
|
+
check.issue(k, "Must be a date like 2026-10-22.");
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (typeof props.min === "string" && typeof props.max === "string" && isIsoDate(props.min) && isIsoDate(props.max) && props.min > props.max) {
|
|
1332
|
+
check.issue("min", "Can't be after the latest date.");
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1144
1335
|
validate: (value, field) => {
|
|
1145
1336
|
if (typeof value !== "string" || !isIsoDate(value.trim())) return "Enter a date like 2026-10-22.";
|
|
1146
1337
|
const date = value.trim();
|
|
@@ -1163,6 +1354,8 @@ var builtins = [
|
|
|
1163
1354
|
value: { json: true },
|
|
1164
1355
|
operators: ["answered", "empty"],
|
|
1165
1356
|
a11y: { semantics: "group" },
|
|
1357
|
+
literal: "boolean",
|
|
1358
|
+
checkProps: (props, check) => check.optionalText(props.text, "text"),
|
|
1166
1359
|
// Unchecked is unanswered, so a required consent must be checked.
|
|
1167
1360
|
isEmpty: (value) => value === void 0 || value === null || value === false,
|
|
1168
1361
|
validate: (value) => value === true ? null : "Check the box to agree.",
|
|
@@ -1212,7 +1405,7 @@ function dateLimits(field) {
|
|
|
1212
1405
|
if (typeof max === "string" && isIsoDate(max)) out.max = max;
|
|
1213
1406
|
return out;
|
|
1214
1407
|
}
|
|
1215
|
-
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType])
|
|
1408
|
+
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType]) registerBuiltIn(manifest);
|
|
1216
1409
|
|
|
1217
1410
|
// src/events.ts
|
|
1218
1411
|
function isPlainObject(value) {
|
|
@@ -1429,8 +1622,8 @@ function fieldMap(def) {
|
|
|
1429
1622
|
}
|
|
1430
1623
|
|
|
1431
1624
|
// src/options.ts
|
|
1432
|
-
var
|
|
1433
|
-
var
|
|
1625
|
+
var isChoice = isChoiceField;
|
|
1626
|
+
var isMultiple = (field) => choiceKind(field.type)?.multiple === true;
|
|
1434
1627
|
function put(record, key, value) {
|
|
1435
1628
|
Object.defineProperty(record, key, { value, enumerable: true, writable: true, configurable: true });
|
|
1436
1629
|
}
|
|
@@ -1508,7 +1701,7 @@ function isPrefillParam(param) {
|
|
|
1508
1701
|
return typeof param === "string" && param.length <= MAX_PREFILL_PARAM_LENGTH && /^[A-Za-z0-9_.-]+$/.test(param);
|
|
1509
1702
|
}
|
|
1510
1703
|
function prefillSetting(field) {
|
|
1511
|
-
if (!
|
|
1704
|
+
if (!choiceKind(field.type)) return void 0;
|
|
1512
1705
|
const raw = field.props?.prefill;
|
|
1513
1706
|
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !isPrefillParam(raw.param)) return void 0;
|
|
1514
1707
|
return { param: raw.param, hideWhenPrefilled: raw.hideWhenPrefilled === true };
|
|
@@ -1536,7 +1729,7 @@ function prefillFromLink(definition, params, values = {}) {
|
|
|
1536
1729
|
if (!raw.length) continue;
|
|
1537
1730
|
const shown2 = new Set(visibleOptions(definition, block, seen).map((o) => o.value));
|
|
1538
1731
|
let value;
|
|
1539
|
-
if (block
|
|
1732
|
+
if (isMultiple(block)) {
|
|
1540
1733
|
const picked = [];
|
|
1541
1734
|
for (const r of raw) {
|
|
1542
1735
|
for (const v of shown2.has(r) ? [r] : r.split(",").map((s) => s.trim())) {
|
|
@@ -1607,8 +1800,13 @@ function validateField(field, value, options = {}) {
|
|
|
1607
1800
|
}
|
|
1608
1801
|
const manifest = getFieldType(field.type);
|
|
1609
1802
|
if (!manifest) return `Unknown field type "${field.type}".`;
|
|
1610
|
-
|
|
1611
|
-
|
|
1803
|
+
let builtIn2;
|
|
1804
|
+
try {
|
|
1805
|
+
builtIn2 = manifest.validate?.(value, field) ?? null;
|
|
1806
|
+
} catch {
|
|
1807
|
+
builtIn2 = VALIDATOR_FAILED_MESSAGE;
|
|
1808
|
+
}
|
|
1809
|
+
if (builtIn2) return builtIn2;
|
|
1612
1810
|
return options.custom ? runCustom(options.custom, value) : null;
|
|
1613
1811
|
}
|
|
1614
1812
|
|
|
@@ -2092,25 +2290,6 @@ function shown(v) {
|
|
|
2092
2290
|
}
|
|
2093
2291
|
var isObject2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
2094
2292
|
var isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
2095
|
-
var LITERAL_TYPE = {
|
|
2096
|
-
"short-text": "string",
|
|
2097
|
-
"long-text": "string",
|
|
2098
|
-
email: "string",
|
|
2099
|
-
phone: "string",
|
|
2100
|
-
rating: "number",
|
|
2101
|
-
nps: "number",
|
|
2102
|
-
"single-choice": "string",
|
|
2103
|
-
"multi-choice": "string",
|
|
2104
|
-
"yes-no": "boolean",
|
|
2105
|
-
list: "string",
|
|
2106
|
-
dropdown: "string",
|
|
2107
|
-
number: "number",
|
|
2108
|
-
date: "string",
|
|
2109
|
-
consent: "boolean",
|
|
2110
|
-
// A calculated field holds a number unless its format says text; see `literalType`.
|
|
2111
|
-
calculated: "number"
|
|
2112
|
-
};
|
|
2113
|
-
var CHOICE_TYPES = /* @__PURE__ */ new Set(["single-choice", "multi-choice", "dropdown"]);
|
|
2114
2293
|
var ORDERING = /* @__PURE__ */ new Set(["lt", "lte", "gt", "gte"]);
|
|
2115
2294
|
var OPERATORS = /* @__PURE__ */ new Set([
|
|
2116
2295
|
"eq",
|
|
@@ -2124,8 +2303,11 @@ var OPERATORS = /* @__PURE__ */ new Set([
|
|
|
2124
2303
|
"answered",
|
|
2125
2304
|
"empty"
|
|
2126
2305
|
]);
|
|
2127
|
-
var RESERVED = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
2128
2306
|
var MAX_CONDITION_DEPTH = 32;
|
|
2307
|
+
function contentKinds() {
|
|
2308
|
+
const names = listContentTypes().map((c) => c.type);
|
|
2309
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "a registered content type";
|
|
2310
|
+
}
|
|
2129
2311
|
function parseForm(input) {
|
|
2130
2312
|
const issues = [];
|
|
2131
2313
|
const warnings = [];
|
|
@@ -2155,7 +2337,7 @@ function parseForm(input) {
|
|
|
2155
2337
|
}
|
|
2156
2338
|
const fits = (x) => typeof x === "string" && x.length <= OPTION_LIMITS.attributeValue;
|
|
2157
2339
|
for (const [name2, v] of Object.entries(attributes)) {
|
|
2158
|
-
if (!name2.trim() || name2.length > OPTION_LIMITS.attributeName ||
|
|
2340
|
+
if (!name2.trim() || name2.length > OPTION_LIMITS.attributeName || RESERVED_NAMES.has(name2)) {
|
|
2159
2341
|
issue(`${at}.attributes`, `${shown(name2)} isn't a usable attribute name (at most ${OPTION_LIMITS.attributeName} characters).`);
|
|
2160
2342
|
} else if (!fits(v) && !(Array.isArray(v) && v.length <= OPTION_LIMITS.attributeItems && v.every(fits))) {
|
|
2161
2343
|
issue(
|
|
@@ -2170,104 +2352,18 @@ function parseForm(input) {
|
|
|
2170
2352
|
issue(path, `${what} must be a non-empty string.`);
|
|
2171
2353
|
return false;
|
|
2172
2354
|
}
|
|
2173
|
-
if (
|
|
2355
|
+
if (RESERVED_NAMES.has(v)) {
|
|
2174
2356
|
issue(path, `"${v}" is reserved and cannot be used as ${what.toLowerCase()}.`);
|
|
2175
2357
|
return false;
|
|
2176
2358
|
}
|
|
2177
2359
|
return true;
|
|
2178
2360
|
};
|
|
2179
|
-
const
|
|
2180
|
-
if (
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
const structuredProps = (type, props, path) => {
|
|
2186
|
-
if (type === "list") {
|
|
2187
|
-
optionalInt(props.min, `${path}.min`, 0);
|
|
2188
|
-
optionalInt(props.max, `${path}.max`, 1);
|
|
2189
|
-
optionalInt(props.itemMaxLength, `${path}.itemMaxLength`, 1);
|
|
2190
|
-
optionalText(props.placeholder, `${path}.placeholder`);
|
|
2191
|
-
ordered(props.min, props.max ?? 10, `${path}.min`, "most items");
|
|
2192
|
-
} else if (type === "table") {
|
|
2193
|
-
const columns = props.columns;
|
|
2194
|
-
if (!Array.isArray(columns) || columns.length < 1 || columns.length > MAX_TABLE_COLUMNS) {
|
|
2195
|
-
issue(`${path}.columns`, `Must be a list of 1 to ${MAX_TABLE_COLUMNS} columns.`);
|
|
2196
|
-
} else {
|
|
2197
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2198
|
-
columns.forEach((c, j) => {
|
|
2199
|
-
const at = `${path}.columns[${j}]`;
|
|
2200
|
-
if (!isObject2(c)) return issue(at, "Each column needs a key and a label.");
|
|
2201
|
-
if (name(c.key, `${at}.key`, "A column key")) {
|
|
2202
|
-
if (seen.has(c.key)) issue(`${at}.key`, `Duplicate column key "${c.key}".`);
|
|
2203
|
-
seen.add(c.key);
|
|
2204
|
-
}
|
|
2205
|
-
text(c.label, `${at}.label`);
|
|
2206
|
-
optionalBoolean(c.long, `${at}.long`);
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
optionalInt(props.minRows, `${path}.minRows`, 0);
|
|
2210
|
-
optionalInt(props.maxRows, `${path}.maxRows`, 1);
|
|
2211
|
-
ordered(props.minRows, props.maxRows ?? 3, `${path}.minRows`, "most rows");
|
|
2212
|
-
} else if (type === "media-notes") {
|
|
2213
|
-
optionalInt(props.maxNotes, `${path}.maxNotes`, 1);
|
|
2214
|
-
optionalInt(props.textMaxLength, `${path}.textMaxLength`, 1);
|
|
2215
|
-
} else if (type === "text-highlights") {
|
|
2216
|
-
optionalText(props.text, `${path}.text`);
|
|
2217
|
-
optionalInt(props.maxHighlights, `${path}.maxHighlights`, 1);
|
|
2218
|
-
} else if (type === CALCULATED_TYPE) {
|
|
2219
|
-
if (props.format !== void 0 && props.format !== "number" && props.format !== "percent" && props.format !== "text") {
|
|
2220
|
-
issue(`${path}.format`, "Must be number, percent or text.");
|
|
2221
|
-
}
|
|
2222
|
-
if (props.decimals !== void 0 && !(isInt(props.decimals) && props.decimals >= 0 && props.decimals <= MAX_CALCULATED_DECIMALS)) {
|
|
2223
|
-
issue(`${path}.decimals`, `Must be a whole number from 0 to ${MAX_CALCULATED_DECIMALS}.`);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
};
|
|
2227
|
-
const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2228
|
-
const fieldProps = (type, props, path) => {
|
|
2229
|
-
if (type === "rating" || type === "nps") {
|
|
2230
|
-
optionalText(props.lowLabel, `${path}.lowLabel`);
|
|
2231
|
-
optionalText(props.highLabel, `${path}.highLabel`);
|
|
2232
|
-
}
|
|
2233
|
-
if (type === "rating" && props.display !== void 0 && props.display !== "numbers" && props.display !== "stars") {
|
|
2234
|
-
issue(`${path}.display`, "Must be numbers or stars.");
|
|
2235
|
-
}
|
|
2236
|
-
if (type === "number") {
|
|
2237
|
-
for (const k of ["min", "max", "step"]) {
|
|
2238
|
-
if (props[k] !== void 0 && !isFiniteNumber(props[k])) issue(`${path}.${k}`, "Must be a number.");
|
|
2239
|
-
}
|
|
2240
|
-
if (isFiniteNumber(props.step) && props.step <= 0) issue(`${path}.step`, "Must be above 0.");
|
|
2241
|
-
if (isFiniteNumber(props.min) && isFiniteNumber(props.max) && props.min > props.max) {
|
|
2242
|
-
issue(`${path}.min`, "Can't be more than the largest number.");
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
if (type === "date") {
|
|
2246
|
-
for (const k of ["min", "max"]) {
|
|
2247
|
-
if (props[k] !== void 0 && !(typeof props[k] === "string" && isIsoDate(props[k]))) {
|
|
2248
|
-
issue(`${path}.${k}`, "Must be a date like 2026-10-22.");
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
if (typeof props.min === "string" && typeof props.max === "string" && isIsoDate(props.min) && isIsoDate(props.max) && props.min > props.max) {
|
|
2252
|
-
issue(`${path}.min`, "Can't be after the latest date.");
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
if (type === "consent") optionalText(props.text, `${path}.text`);
|
|
2256
|
-
if (type === PHOTO_TYPE) {
|
|
2257
|
-
if (props.maxFiles !== void 0 && !(isInt(props.maxFiles) && props.maxFiles >= 1 && props.maxFiles <= MAX_PHOTO_FILES)) {
|
|
2258
|
-
issue(`${path}.maxFiles`, `Must be a whole number from 1 to ${MAX_PHOTO_FILES}.`);
|
|
2259
|
-
}
|
|
2260
|
-
if (props.maxSizeMb !== void 0 && !(isInt(props.maxSizeMb) && props.maxSizeMb >= 1 && props.maxSizeMb <= MAX_PHOTO_SIZE_MB)) {
|
|
2261
|
-
issue(`${path}.maxSizeMb`, `Must be a whole number from 1 to ${MAX_PHOTO_SIZE_MB}.`);
|
|
2262
|
-
}
|
|
2263
|
-
if (props.formats !== void 0) {
|
|
2264
|
-
const formats = props.formats;
|
|
2265
|
-
if (!Array.isArray(formats) || formats.length === 0 || !formats.every(isPhotoFormat)) {
|
|
2266
|
-
issue(`${path}.formats`, "Must be a list of one or more of jpeg, png, heic and webp.");
|
|
2267
|
-
} else if (new Set(formats).size !== formats.length) {
|
|
2268
|
-
issue(`${path}.formats`, "Name each format once.");
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2361
|
+
const kindProps = (check, props, path) => {
|
|
2362
|
+
if (!check) return;
|
|
2363
|
+
try {
|
|
2364
|
+
check(props, propsChecker(path, issue));
|
|
2365
|
+
} catch {
|
|
2366
|
+
issue(path, "These settings couldn't be checked.");
|
|
2271
2367
|
}
|
|
2272
2368
|
};
|
|
2273
2369
|
if (!isObject2(input)) {
|
|
@@ -2306,7 +2402,7 @@ function parseForm(input) {
|
|
|
2306
2402
|
const order = /* @__PURE__ */ new Map();
|
|
2307
2403
|
const tableColumnKeys = /* @__PURE__ */ new Map();
|
|
2308
2404
|
const choices = /* @__PURE__ */ new Map();
|
|
2309
|
-
const
|
|
2405
|
+
const literals = /* @__PURE__ */ new Map();
|
|
2310
2406
|
input.blocks.forEach((block, i) => {
|
|
2311
2407
|
const path = `blocks[${i}]`;
|
|
2312
2408
|
if (!isObject2(block) || typeof block.id !== "string" || !block.id) {
|
|
@@ -2334,7 +2430,7 @@ function parseForm(input) {
|
|
|
2334
2430
|
if (from !== void 0) {
|
|
2335
2431
|
if (!isObject2(from) || from.source !== "host" || typeof from.key !== "string" || !from.key) {
|
|
2336
2432
|
issue(`${path}.props.optionsFrom`, 'Must be { source: "host", key } with a non-empty key.');
|
|
2337
|
-
} else if (typeof block.type === "string" && !
|
|
2433
|
+
} else if (typeof block.type === "string" && !choiceKind(block.type)) {
|
|
2338
2434
|
warnings.push({ path: `${path}.props.optionsFrom`, message: "Only single choice, multiple choice and dropdown questions take options from the app." });
|
|
2339
2435
|
}
|
|
2340
2436
|
const filter = isObject2(from) ? from.filter : void 0;
|
|
@@ -2356,7 +2452,7 @@ function parseForm(input) {
|
|
|
2356
2452
|
);
|
|
2357
2453
|
} else {
|
|
2358
2454
|
optionalBoolean(prefill.hideWhenPrefilled, `${path}.props.prefill.hideWhenPrefilled`);
|
|
2359
|
-
if (typeof block.type === "string" && !
|
|
2455
|
+
if (typeof block.type === "string" && !choiceKind(block.type)) {
|
|
2360
2456
|
warnings.push({ path: `${path}.props.prefill`, message: "Only single choice, multiple choice and dropdown questions are filled in from a link." });
|
|
2361
2457
|
}
|
|
2362
2458
|
}
|
|
@@ -2387,7 +2483,7 @@ function parseForm(input) {
|
|
|
2387
2483
|
if (other !== void 0) {
|
|
2388
2484
|
if (other !== true && other !== false && !(isObject2(other) && (other.label === void 0 || typeof other.label === "string"))) {
|
|
2389
2485
|
issue(`${path}.props.other`, "Must be true, false or { label }.");
|
|
2390
|
-
} else if (other !== false && typeof block.type === "string" && !
|
|
2486
|
+
} else if (other !== false && typeof block.type === "string" && !choiceKind(block.type)) {
|
|
2391
2487
|
warnings.push({ path: `${path}.props.other`, message: 'Only single choice, multiple choice and dropdown questions take an "Other" answer.' });
|
|
2392
2488
|
}
|
|
2393
2489
|
}
|
|
@@ -2400,19 +2496,14 @@ function parseForm(input) {
|
|
|
2400
2496
|
}
|
|
2401
2497
|
}
|
|
2402
2498
|
if (typeof block.type === "string" && (block.props === void 0 || isObject2(block.props))) {
|
|
2403
|
-
|
|
2404
|
-
fieldProps(block.type, block.props ?? {}, `${path}.props`);
|
|
2499
|
+
kindProps(getFieldType(block.type)?.checkProps, block.props ?? {}, `${path}.props`);
|
|
2405
2500
|
}
|
|
2406
2501
|
if (validKey) {
|
|
2407
2502
|
if (keys.has(block.key)) issue(`${path}.key`, `Duplicate key "${String(block.key)}".`);
|
|
2408
2503
|
keys.set(block.key, block.id);
|
|
2409
2504
|
}
|
|
2410
2505
|
fieldTypes.set(block.id, typeof block.type === "string" ? block.type : "");
|
|
2411
|
-
|
|
2412
|
-
const format = isObject2(block.props) ? block.props.format : void 0;
|
|
2413
|
-
if (format === "text") calculatedLiteral.set(block.id, "string");
|
|
2414
|
-
else if (format !== "number" && format !== "percent") calculatedLiteral.set(block.id, void 0);
|
|
2415
|
-
}
|
|
2506
|
+
literals.set(block.id, typeof block.type === "string" ? literalType(block.type, isObject2(block.props) ? block.props : {}) : void 0);
|
|
2416
2507
|
if (block.type === "table" && isObject2(block.props) && Array.isArray(block.props.columns)) {
|
|
2417
2508
|
tableColumnKeys.set(
|
|
2418
2509
|
block.id,
|
|
@@ -2420,8 +2511,13 @@ function parseForm(input) {
|
|
|
2420
2511
|
);
|
|
2421
2512
|
}
|
|
2422
2513
|
} else if (block.kind === "content") {
|
|
2423
|
-
|
|
2514
|
+
const content = typeof block.type === "string" ? getContentType(block.type) : void 0;
|
|
2515
|
+
if (!content) issue(`${path}.type`, `Must be ${contentKinds()}.`);
|
|
2424
2516
|
text(block.text, `${path}.text`);
|
|
2517
|
+
if (content?.checkProps) {
|
|
2518
|
+
if (block.props === void 0 || isObject2(block.props) && isJson(block.props)) kindProps(content.checkProps, block.props ?? {}, `${path}.props`);
|
|
2519
|
+
else issue(`${path}.props`, "Must be a JSON object.");
|
|
2520
|
+
}
|
|
2425
2521
|
} else if (block.kind === "page-break") {
|
|
2426
2522
|
optionalText(block.title, `${path}.title`);
|
|
2427
2523
|
} else {
|
|
@@ -2551,8 +2647,8 @@ function parseForm(input) {
|
|
|
2551
2647
|
if (typeof left.field === "string" && fieldTypes.has(left.field)) {
|
|
2552
2648
|
const fieldType = fieldTypes.get(left.field);
|
|
2553
2649
|
what = `"${keyOf(left.field)}"`;
|
|
2554
|
-
type =
|
|
2555
|
-
if (
|
|
2650
|
+
type = literals.get(left.field);
|
|
2651
|
+
if (choiceKind(fieldType)) options = choices.get(left.field);
|
|
2556
2652
|
} else if (typeof left.hidden === "string" && hiddenTypes.has(left.hidden)) {
|
|
2557
2653
|
what = `hidden input "${left.hidden}"`;
|
|
2558
2654
|
type = hiddenTypes.get(left.hidden);
|