@marktiderman/genesis-forms 0.4.2 → 0.5.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 +67 -0
- package/dist/destinations.cjs +92 -4
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +1 -1
- package/dist/destinations.d.ts +1 -1
- package/dist/destinations.js +92 -4
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-Dz9J5ej2.d.ts → engine-BDq0XJ21.d.cts} +25 -2
- package/dist/{engine-BkNuzlna.d.cts → engine-Qzp6bX6O.d.ts} +25 -2
- package/dist/index.cjs +259 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -75
- package/dist/index.d.ts +120 -75
- package/dist/index.js +236 -17
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +187 -12
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +10 -3
- package/dist/server.d.ts +10 -3
- package/dist/server.js +187 -12
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +187 -12
- 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 +187 -12
- package/dist/testing.js.map +1 -1
- package/dist/{types-BBKBQ8sp.d.cts → types-BhYUbLJ-.d.cts} +21 -1
- package/dist/{types-BBKBQ8sp.d.ts → types-BhYUbLJ-.d.ts} +21 -1
- package/dist/validate-Bs1lyoOl.d.ts +131 -0
- package/dist/validate-BsZ0YTQS.d.cts +131 -0
- package/package.json +1 -1
- package/dist/validate-BXXqi0fq.d.cts +0 -30
- package/dist/validate-BjNwF1UE.d.ts +0 -30
package/dist/index.js
CHANGED
|
@@ -833,6 +833,81 @@ var structuredFieldTypes = [
|
|
|
833
833
|
}
|
|
834
834
|
];
|
|
835
835
|
|
|
836
|
+
// src/photo.ts
|
|
837
|
+
var PHOTO_TYPE = "photo";
|
|
838
|
+
var UPLOAD_FIELD_TYPES = [PHOTO_TYPE];
|
|
839
|
+
var PHOTO_FORMATS = {
|
|
840
|
+
jpeg: ["image/jpeg"],
|
|
841
|
+
png: ["image/png"],
|
|
842
|
+
heic: ["image/heic", "image/heif"],
|
|
843
|
+
webp: ["image/webp"]
|
|
844
|
+
};
|
|
845
|
+
var FORMAT_NAMES = { jpeg: "JPEG", png: "PNG", heic: "HEIC", webp: "WebP" };
|
|
846
|
+
var ALL_FORMATS = Object.keys(PHOTO_FORMATS);
|
|
847
|
+
var DEFAULT_PHOTO_FILES = 3;
|
|
848
|
+
var MAX_PHOTO_FILES = 5;
|
|
849
|
+
var DEFAULT_PHOTO_SIZE_MB = 10;
|
|
850
|
+
var MAX_PHOTO_SIZE_MB = 25;
|
|
851
|
+
var MAX_PHOTO_ID_LENGTH = 200;
|
|
852
|
+
var MAX_PHOTO_NAME_LENGTH = 255;
|
|
853
|
+
var isPhotoFormat = (v) => typeof v === "string" && Object.prototype.hasOwnProperty.call(PHOTO_FORMATS, v);
|
|
854
|
+
var wholeIn = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
855
|
+
function photoLimits(field2) {
|
|
856
|
+
const maxFiles = wholeIn(field2.props?.maxFiles, 1, MAX_PHOTO_FILES) ? field2.props.maxFiles : DEFAULT_PHOTO_FILES;
|
|
857
|
+
const maxSizeMb = wholeIn(field2.props?.maxSizeMb, 1, MAX_PHOTO_SIZE_MB) ? field2.props.maxSizeMb : DEFAULT_PHOTO_SIZE_MB;
|
|
858
|
+
const raw = field2.props?.formats;
|
|
859
|
+
const picked = Array.isArray(raw) ? ALL_FORMATS.filter((f2) => raw.includes(f2)) : [];
|
|
860
|
+
const formats = picked.length ? picked : [...ALL_FORMATS];
|
|
861
|
+
return {
|
|
862
|
+
maxFiles,
|
|
863
|
+
maxSizeMb,
|
|
864
|
+
maxBytes: maxSizeMb * 1024 * 1024,
|
|
865
|
+
formats,
|
|
866
|
+
mimeTypes: formats.flatMap((f2) => PHOTO_FORMATS[f2])
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function isPhotoRef(v) {
|
|
870
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
871
|
+
const r = v;
|
|
872
|
+
return typeof r.id === "string" && r.id.length > 0 && r.id.length <= MAX_PHOTO_ID_LENGTH && typeof r.name === "string" && r.name.length <= MAX_PHOTO_NAME_LENGTH && typeof r.type === "string" && typeof r.size === "number" && Number.isInteger(r.size) && r.size > 0;
|
|
873
|
+
}
|
|
874
|
+
function photoRefs(value) {
|
|
875
|
+
if (!Array.isArray(value)) return [];
|
|
876
|
+
const refs = [];
|
|
877
|
+
for (const r of value) if (isPhotoRef(r)) refs.push({ id: r.id, name: r.name, type: r.type, size: r.size });
|
|
878
|
+
return refs;
|
|
879
|
+
}
|
|
880
|
+
var plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
881
|
+
function photoFormatList(formats) {
|
|
882
|
+
const names = formats.map((f2) => FORMAT_NAMES[f2]);
|
|
883
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "";
|
|
884
|
+
}
|
|
885
|
+
var photoTypeMessage = (field2) => `Use a ${photoFormatList(photoLimits(field2).formats)} photo.`;
|
|
886
|
+
var photoSizeMessage = (field2) => `Each photo must be ${photoLimits(field2).maxSizeMb} MB or smaller.`;
|
|
887
|
+
var photoCountMessage = (field2) => `Add at most ${plural2(photoLimits(field2).maxFiles, "photo")}.`;
|
|
888
|
+
var photoFieldType = {
|
|
889
|
+
type: PHOTO_TYPE,
|
|
890
|
+
label: "Photo",
|
|
891
|
+
category: "media",
|
|
892
|
+
value: { json: true },
|
|
893
|
+
operators: ["answered", "empty"],
|
|
894
|
+
a11y: { semantics: "group" },
|
|
895
|
+
isEmpty: (value) => value === void 0 || value === null || Array.isArray(value) && value.length === 0,
|
|
896
|
+
validate: (value, field2) => {
|
|
897
|
+
if (!Array.isArray(value) || !value.every(isPhotoRef)) return "Add a photo.";
|
|
898
|
+
const limits = photoLimits(field2);
|
|
899
|
+
if (value.length > limits.maxFiles) return photoCountMessage(field2);
|
|
900
|
+
const refs = value;
|
|
901
|
+
if (new Set(refs.map((r) => r.id)).size !== refs.length) return "Each photo can be added once.";
|
|
902
|
+
if (refs.some((r) => !limits.mimeTypes.includes(r.type.toLowerCase()))) return photoTypeMessage(field2);
|
|
903
|
+
if (refs.some((r) => r.size > limits.maxBytes)) return photoSizeMessage(field2);
|
|
904
|
+
return null;
|
|
905
|
+
},
|
|
906
|
+
normalize: (value) => Array.isArray(value) && value.every(isPhotoRef) ? photoRefs(value) : value,
|
|
907
|
+
summarize: (value) => photoRefs(value).map((r) => r.name || "Photo").join(", "),
|
|
908
|
+
samples: () => [[{ id: "sample-photo", name: "photo.jpg", type: "image/jpeg", size: 1024 }]]
|
|
909
|
+
};
|
|
910
|
+
|
|
836
911
|
// src/fields.ts
|
|
837
912
|
var REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/field-types");
|
|
838
913
|
var globalScope = globalThis;
|
|
@@ -875,6 +950,20 @@ function summarizeValue(value, field2) {
|
|
|
875
950
|
if (Array.isArray(value)) return value.join(", ");
|
|
876
951
|
return String(value);
|
|
877
952
|
}
|
|
953
|
+
var OTHER_MAX_LENGTH = 200;
|
|
954
|
+
function otherOption(field2) {
|
|
955
|
+
const other = field2.props?.other;
|
|
956
|
+
if (other === true) return { label: "Other" };
|
|
957
|
+
if (other && typeof other === "object" && !Array.isArray(other)) {
|
|
958
|
+
const label = typeof other.label === "string" && other.label.trim() ? other.label.trim() : "Other";
|
|
959
|
+
return { label };
|
|
960
|
+
}
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
function isOtherValue(field2, value) {
|
|
964
|
+
return otherOption(field2) !== null && typeof value === "string" && !choiceOptions(field2).some((o) => o.value === value);
|
|
965
|
+
}
|
|
966
|
+
var typedOther = (field2, value) => isOtherValue(field2, value) && value.trim() !== "" && value.length <= OTHER_MAX_LENGTH;
|
|
878
967
|
function choiceOptions(field2) {
|
|
879
968
|
const raw = field2.props?.options;
|
|
880
969
|
if (!Array.isArray(raw)) return [];
|
|
@@ -882,6 +971,42 @@ function choiceOptions(field2) {
|
|
|
882
971
|
(o) => o && typeof o === "object" && !Array.isArray(o) && typeof o.value === "string" ? [{ value: o.value, label: typeof o.label === "string" ? o.label : void 0 }] : []
|
|
883
972
|
);
|
|
884
973
|
}
|
|
974
|
+
function optionsSource(field2) {
|
|
975
|
+
const raw = field2.props?.optionsFrom;
|
|
976
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
977
|
+
return raw.source === "host" && typeof raw.key === "string" && raw.key ? { source: "host", key: raw.key } : void 0;
|
|
978
|
+
}
|
|
979
|
+
function hostList(hostOptions, key) {
|
|
980
|
+
if (!hostOptions || !Object.prototype.hasOwnProperty.call(hostOptions, key)) return [];
|
|
981
|
+
const list = hostOptions[key];
|
|
982
|
+
if (!Array.isArray(list)) return [];
|
|
983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
984
|
+
const out = [];
|
|
985
|
+
for (const o of list) {
|
|
986
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) continue;
|
|
987
|
+
const { value, label } = o;
|
|
988
|
+
if (typeof value !== "string" || !value || seen.has(value)) continue;
|
|
989
|
+
seen.add(value);
|
|
990
|
+
out.push(typeof label === "string" ? { value, label } : { value });
|
|
991
|
+
}
|
|
992
|
+
return out;
|
|
993
|
+
}
|
|
994
|
+
function withHostOptions(definition, hostOptions) {
|
|
995
|
+
let changed = false;
|
|
996
|
+
const blocks = definition.blocks.map((block) => {
|
|
997
|
+
if (block.kind !== "field") return block;
|
|
998
|
+
const from = optionsSource(block);
|
|
999
|
+
if (!from) return block;
|
|
1000
|
+
const own2 = choiceOptions(block);
|
|
1001
|
+
const taken = new Set(own2.map((o) => o.value));
|
|
1002
|
+
const added = hostList(hostOptions, from.key).filter((o) => !taken.has(o.value));
|
|
1003
|
+
if (!added.length) return block;
|
|
1004
|
+
changed = true;
|
|
1005
|
+
const options = [...own2, ...added].map((o) => o.label === void 0 ? { value: o.value } : { value: o.value, label: o.label });
|
|
1006
|
+
return { ...block, props: { ...block.props, options } };
|
|
1007
|
+
});
|
|
1008
|
+
return changed ? { ...definition, blocks } : definition;
|
|
1009
|
+
}
|
|
885
1010
|
function optionLabel(field2, value) {
|
|
886
1011
|
const option = choiceOptions(field2).find((o) => o.value === value);
|
|
887
1012
|
return option?.label ?? value;
|
|
@@ -1000,7 +1125,7 @@ var builtins = [
|
|
|
1000
1125
|
value: { json: true },
|
|
1001
1126
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
1002
1127
|
a11y: { semantics: "group" },
|
|
1003
|
-
validate: (value, field2) => typeof value === "string" && choiceOptions(field2).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
1128
|
+
validate: (value, field2) => typeof value === "string" && (choiceOptions(field2).some((o) => o.value === value) || typedOther(field2, value)) ? null : "Choose one of the options.",
|
|
1004
1129
|
summarize: (value, field2) => optionLabel(field2, String(value))
|
|
1005
1130
|
},
|
|
1006
1131
|
{
|
|
@@ -1012,7 +1137,8 @@ var builtins = [
|
|
|
1012
1137
|
a11y: { semantics: "group" },
|
|
1013
1138
|
validate: (value, field2) => {
|
|
1014
1139
|
const allowed = new Set(choiceOptions(field2).map((o) => o.value));
|
|
1015
|
-
|
|
1140
|
+
const others = Array.isArray(value) ? value.filter((v) => typeof v === "string" && !allowed.has(v)) : [];
|
|
1141
|
+
return Array.isArray(value) && new Set(value).size === value.length && value.every((v) => typeof v === "string") && (others.length === 0 || others.length === 1 && typedOther(field2, others[0])) ? null : "Choose from the options.";
|
|
1016
1142
|
},
|
|
1017
1143
|
summarize: (value, field2) => Array.isArray(value) ? value.map((v) => optionLabel(field2, String(v))).join(", ") : String(value)
|
|
1018
1144
|
},
|
|
@@ -1033,7 +1159,7 @@ var builtins = [
|
|
|
1033
1159
|
value: { json: true },
|
|
1034
1160
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
1035
1161
|
a11y: { semantics: "control" },
|
|
1036
|
-
validate: (value, field2) => typeof value === "string" && choiceOptions(field2).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
1162
|
+
validate: (value, field2) => typeof value === "string" && (choiceOptions(field2).some((o) => o.value === value) || typedOther(field2, value)) ? null : "Choose one of the options.",
|
|
1037
1163
|
summarize: (value, field2) => optionLabel(field2, String(value))
|
|
1038
1164
|
},
|
|
1039
1165
|
{
|
|
@@ -1143,7 +1269,7 @@ function dateLimits(field2) {
|
|
|
1143
1269
|
if (typeof max === "string" && isIsoDate(max)) out.max = max;
|
|
1144
1270
|
return out;
|
|
1145
1271
|
}
|
|
1146
|
-
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType]) registerFieldType(manifest);
|
|
1272
|
+
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType]) registerFieldType(manifest);
|
|
1147
1273
|
|
|
1148
1274
|
// src/logic.ts
|
|
1149
1275
|
function own(record, key) {
|
|
@@ -1443,6 +1569,22 @@ function parseForm(input) {
|
|
|
1443
1569
|
}
|
|
1444
1570
|
}
|
|
1445
1571
|
if (type === "consent") optionalText(props2.text, `${path}.text`);
|
|
1572
|
+
if (type === PHOTO_TYPE) {
|
|
1573
|
+
if (props2.maxFiles !== void 0 && !(isInt(props2.maxFiles) && props2.maxFiles >= 1 && props2.maxFiles <= MAX_PHOTO_FILES)) {
|
|
1574
|
+
issue(`${path}.maxFiles`, `Must be a whole number from 1 to ${MAX_PHOTO_FILES}.`);
|
|
1575
|
+
}
|
|
1576
|
+
if (props2.maxSizeMb !== void 0 && !(isInt(props2.maxSizeMb) && props2.maxSizeMb >= 1 && props2.maxSizeMb <= MAX_PHOTO_SIZE_MB)) {
|
|
1577
|
+
issue(`${path}.maxSizeMb`, `Must be a whole number from 1 to ${MAX_PHOTO_SIZE_MB}.`);
|
|
1578
|
+
}
|
|
1579
|
+
if (props2.formats !== void 0) {
|
|
1580
|
+
const formats = props2.formats;
|
|
1581
|
+
if (!Array.isArray(formats) || formats.length === 0 || !formats.every(isPhotoFormat)) {
|
|
1582
|
+
issue(`${path}.formats`, "Must be a list of one or more of jpeg, png, heic and webp.");
|
|
1583
|
+
} else if (new Set(formats).size !== formats.length) {
|
|
1584
|
+
issue(`${path}.formats`, "Name each format once.");
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1446
1588
|
};
|
|
1447
1589
|
if (!isObject2(input)) {
|
|
1448
1590
|
issue("", "A form definition must be an object.");
|
|
@@ -1463,6 +1605,7 @@ function parseForm(input) {
|
|
|
1463
1605
|
else {
|
|
1464
1606
|
optionalBoolean(input.settings.anonymous, "settings.anonymous");
|
|
1465
1607
|
optionalBoolean(input.settings.clearHiddenValues, "settings.clearHiddenValues");
|
|
1608
|
+
optionalBoolean(input.settings.saveProgress, "settings.saveProgress");
|
|
1466
1609
|
}
|
|
1467
1610
|
}
|
|
1468
1611
|
if (input.meta !== void 0 && (!isObject2(input.meta) || !isJson(input.meta))) {
|
|
@@ -1503,6 +1646,14 @@ function parseForm(input) {
|
|
|
1503
1646
|
issue(`${path}.type`, `Unknown field type "${String(block.type)}".`);
|
|
1504
1647
|
}
|
|
1505
1648
|
if (block.props !== void 0) {
|
|
1649
|
+
const from = isObject2(block.props) ? block.props.optionsFrom : void 0;
|
|
1650
|
+
if (from !== void 0) {
|
|
1651
|
+
if (!isObject2(from) || from.source !== "host" || typeof from.key !== "string" || !from.key) {
|
|
1652
|
+
issue(`${path}.props.optionsFrom`, 'Must be { source: "host", key } with a non-empty key.');
|
|
1653
|
+
} else if (typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
1654
|
+
warnings.push({ path: `${path}.props.optionsFrom`, message: "Only single choice, multiple choice and dropdown questions take options from the app." });
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1506
1657
|
if (!isObject2(block.props) || !isJson(block.props)) issue(`${path}.props`, "Must be a JSON object.");
|
|
1507
1658
|
else if (block.props.options !== void 0) {
|
|
1508
1659
|
const options = block.props.options;
|
|
@@ -1513,10 +1664,20 @@ function parseForm(input) {
|
|
|
1513
1664
|
if (!isObject2(o) || typeof o.value !== "string") issue(at, "Each option needs a string value.");
|
|
1514
1665
|
else optionalText(o.label, `${at}.label`);
|
|
1515
1666
|
});
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1667
|
+
if (from === void 0) {
|
|
1668
|
+
choices.set(
|
|
1669
|
+
block.id,
|
|
1670
|
+
new Set(options.flatMap((o) => isObject2(o) && typeof o.value === "string" ? [o.value] : []))
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const other = isObject2(block.props) ? block.props.other : void 0;
|
|
1676
|
+
if (other !== void 0) {
|
|
1677
|
+
if (other !== true && other !== false && !(isObject2(other) && (other.label === void 0 || typeof other.label === "string"))) {
|
|
1678
|
+
issue(`${path}.props.other`, "Must be true, false or { label }.");
|
|
1679
|
+
} else if (other !== false && typeof block.type === "string" && !CHOICE_TYPES.has(block.type)) {
|
|
1680
|
+
warnings.push({ path: `${path}.props.other`, message: 'Only single choice, multiple choice and dropdown questions take an "Other" answer.' });
|
|
1520
1681
|
}
|
|
1521
1682
|
}
|
|
1522
1683
|
const { max, maxLength: maxLength2 } = isObject2(block.props) ? block.props : {};
|
|
@@ -1591,6 +1752,8 @@ function parseForm(input) {
|
|
|
1591
1752
|
} else if (ref.column !== void 0) issue(path, `"${ref.key}" is not a table, so it has no column "${ref.column}".`);
|
|
1592
1753
|
else if (targetType === "media-notes" || targetType === "text-highlights") {
|
|
1593
1754
|
warnings.push({ path, message: `"${ref.key}" holds notes, not numbers; the formula reads it as blank.` });
|
|
1755
|
+
} else if (targetType === PHOTO_TYPE) {
|
|
1756
|
+
warnings.push({ path, message: `"${ref.key}" holds photos, not numbers; the formula reads it as blank.` });
|
|
1594
1757
|
}
|
|
1595
1758
|
}
|
|
1596
1759
|
});
|
|
@@ -1810,9 +1973,15 @@ function field(type, key, options, props2) {
|
|
|
1810
1973
|
return block;
|
|
1811
1974
|
}
|
|
1812
1975
|
var props = (all) => Object.fromEntries(Object.entries(all).filter((e) => e[1] !== void 0));
|
|
1813
|
-
var choiceProps = (
|
|
1814
|
-
|
|
1815
|
-
|
|
1976
|
+
var choiceProps = (o) => {
|
|
1977
|
+
const out = {};
|
|
1978
|
+
if (o.options || !o.optionsFrom) {
|
|
1979
|
+
out.options = (o.options ?? []).map((x) => x.label ? { value: x.value, label: x.label } : { value: x.value });
|
|
1980
|
+
}
|
|
1981
|
+
if (o.optionsFrom) out.optionsFrom = { source: o.optionsFrom.source, key: o.optionsFrom.key };
|
|
1982
|
+
if (o.other) out.other = o.other === true ? true : props({ label: o.other.label });
|
|
1983
|
+
return out;
|
|
1984
|
+
};
|
|
1816
1985
|
var hiddenSpec = (type, o) => ({
|
|
1817
1986
|
type,
|
|
1818
1987
|
source: o.source ?? "url",
|
|
@@ -1836,11 +2005,11 @@ var f = {
|
|
|
1836
2005
|
),
|
|
1837
2006
|
/** A 0-to-10 Net Promoter Score. The end labels default to "Not likely" and "Very likely" in the renderer. */
|
|
1838
2007
|
nps: (key, o) => field("nps", key, o, props({ lowLabel: o.lowLabel || void 0, highLabel: o.highLabel || void 0 })),
|
|
1839
|
-
singleChoice: (key, o) => field("single-choice", key, o, choiceProps(o
|
|
1840
|
-
multiChoice: (key, o) => field("multi-choice", key, o, choiceProps(o
|
|
2008
|
+
singleChoice: (key, o) => field("single-choice", key, o, choiceProps(o)),
|
|
2009
|
+
multiChoice: (key, o) => field("multi-choice", key, o, choiceProps(o)),
|
|
1841
2010
|
yesNo: (key, o) => field("yes-no", key, o),
|
|
1842
2011
|
/** One option from a compact menu: for a long list (a country, a class), where single choice would be a wall of buttons. */
|
|
1843
|
-
dropdown: (key, o) => field("dropdown", key, o, choiceProps(o
|
|
2012
|
+
dropdown: (key, o) => field("dropdown", key, o, choiceProps(o)),
|
|
1844
2013
|
/** A number, optionally between `min` and `max` and on a `step` (from `min`, else 0): `step: 1` for whole numbers. */
|
|
1845
2014
|
number: (key, o) => field("number", key, o, props({ min: o.min, max: o.max, step: o.step })),
|
|
1846
2015
|
/** A calendar day, stored as `YYYY-MM-DD`, optionally between `min` and `max` (also `YYYY-MM-DD`). */
|
|
@@ -1892,6 +2061,17 @@ var f = {
|
|
|
1892
2061
|
o,
|
|
1893
2062
|
props({ text: o.text, maxHighlights: o.maxHighlights })
|
|
1894
2063
|
),
|
|
2064
|
+
/**
|
|
2065
|
+
* Up to a few photos (a picture of broken equipment). The answer holds the
|
|
2066
|
+
* references the host's upload returned, never the bytes; `formats` from
|
|
2067
|
+
* jpeg, png, heic and webp (default all four).
|
|
2068
|
+
*/
|
|
2069
|
+
photo: (key, o) => field(
|
|
2070
|
+
"photo",
|
|
2071
|
+
key,
|
|
2072
|
+
o,
|
|
2073
|
+
props({ maxFiles: o.maxFiles, maxSizeMb: o.maxSizeMb, formats: o.formats ? [...o.formats] : void 0 })
|
|
2074
|
+
),
|
|
1895
2075
|
/** Any registered field type, including plugins. */
|
|
1896
2076
|
custom: (type, key, o) => field(type, key, o, o.props),
|
|
1897
2077
|
heading: (id, text) => ({ kind: "content", id, type: "heading", text }),
|
|
@@ -2075,7 +2255,8 @@ function readsAny(node, ids) {
|
|
|
2075
2255
|
function createForm(definition, options = {}) {
|
|
2076
2256
|
return build(definition, options).engine;
|
|
2077
2257
|
}
|
|
2078
|
-
function build(
|
|
2258
|
+
function build(given, options, replaying = false, budget) {
|
|
2259
|
+
const definition = withHostOptions(given, options.hostOptions);
|
|
2079
2260
|
const fields = fieldMap(definition);
|
|
2080
2261
|
const byKey = new Map([...fields.values()].map((f2) => [f2.key, f2]));
|
|
2081
2262
|
const blocks = new Map(definition.blocks.map((b) => [b.id, b]));
|
|
@@ -2121,6 +2302,7 @@ function build(definition, options, replaying = false, budget) {
|
|
|
2121
2302
|
path: first.path,
|
|
2122
2303
|
current: first.path[0] ?? null,
|
|
2123
2304
|
history: [],
|
|
2305
|
+
furthest: first.path[0] ?? null,
|
|
2124
2306
|
errors: {},
|
|
2125
2307
|
status: "in_progress",
|
|
2126
2308
|
ending: null,
|
|
@@ -2221,7 +2403,8 @@ function build(definition, options, replaying = false, budget) {
|
|
|
2221
2403
|
emit({ ...state, events });
|
|
2222
2404
|
return true;
|
|
2223
2405
|
}
|
|
2224
|
-
|
|
2406
|
+
const furthest = state.furthest && state.path.indexOf(state.furthest) >= state.path.indexOf(target) ? state.furthest : target;
|
|
2407
|
+
emit({ ...state, current: target, history: [...state.history, state.current], furthest, events });
|
|
2225
2408
|
return true;
|
|
2226
2409
|
},
|
|
2227
2410
|
back() {
|
|
@@ -2266,6 +2449,15 @@ function build(definition, options, replaying = false, budget) {
|
|
|
2266
2449
|
settleNow();
|
|
2267
2450
|
batch = wasBatch;
|
|
2268
2451
|
}
|
|
2452
|
+
const resumeAt = options.resumeAt;
|
|
2453
|
+
if (resumeAt && !replaying && state.path.includes(resumeAt)) {
|
|
2454
|
+
for (let i = 0; i < state.path.length && state.current !== resumeAt; i++) {
|
|
2455
|
+
if (!engine.next()) {
|
|
2456
|
+
state = { ...state, errors: {} };
|
|
2457
|
+
break;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2269
2461
|
const apply = (event) => {
|
|
2270
2462
|
if (event.type === "answer") {
|
|
2271
2463
|
if (fields.has(event.field)) {
|
|
@@ -2305,6 +2497,33 @@ function replayWithin(definition, events, options, maxWork) {
|
|
|
2305
2497
|
return engine;
|
|
2306
2498
|
}
|
|
2307
2499
|
|
|
2500
|
+
// src/progress.ts
|
|
2501
|
+
function furthestReached(state, definition) {
|
|
2502
|
+
const at = (id) => id ? state.path.indexOf(id) : -1;
|
|
2503
|
+
let furthest = Math.max(at(state.current), at(state.furthest));
|
|
2504
|
+
for (const id of state.history) furthest = Math.max(furthest, at(id));
|
|
2505
|
+
const fields = fieldMap(definition);
|
|
2506
|
+
for (let i = state.path.length - 1; i > furthest; i--) {
|
|
2507
|
+
const field2 = fields.get(state.path[i]);
|
|
2508
|
+
if (field2 && !isEmptyValue(own(state.values, field2.id), field2)) {
|
|
2509
|
+
furthest = i;
|
|
2510
|
+
break;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
return furthest >= 0 ? state.path[furthest] : null;
|
|
2514
|
+
}
|
|
2515
|
+
function formProgress(engine) {
|
|
2516
|
+
const state = engine.getState();
|
|
2517
|
+
const furthest = furthestReached(state, engine.definition);
|
|
2518
|
+
const index = furthest ? state.path.indexOf(furthest) : -1;
|
|
2519
|
+
const fields = fieldMap(engine.definition);
|
|
2520
|
+
let fieldId = null;
|
|
2521
|
+
for (let i = index; i >= 0 && fieldId === null; i--) {
|
|
2522
|
+
if (fields.has(state.path[i])) fieldId = state.path[i];
|
|
2523
|
+
}
|
|
2524
|
+
return { step: index + 1, total: state.path.length, fieldId };
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2308
2527
|
// src/explain.ts
|
|
2309
2528
|
function explain(source, blockId, options = {}) {
|
|
2310
2529
|
const input = "getState" in source ? { definition: source.definition, values: source.getState().values, hidden: source.getState().hidden } : source;
|
|
@@ -2441,6 +2660,6 @@ function describeCondition(condition, def, fields = fieldMap(def)) {
|
|
|
2441
2660
|
return `${left} ${op} ${right}`;
|
|
2442
2661
|
}
|
|
2443
2662
|
|
|
2444
|
-
export { CALCULATED_TYPE, COMPUTED_FIELD_TYPES, FORMULA_FUNCTIONS, HIGHLIGHT_NOTE_MESSAGE, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, REQUIRED_MESSAGE, STRUCTURED_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, createForm, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isFormEvent, isIsoDate, isJson, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, replay, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, validateField, when };
|
|
2663
|
+
export { CALCULATED_TYPE, COMPUTED_FIELD_TYPES, DEFAULT_PHOTO_FILES, DEFAULT_PHOTO_SIZE_MB, FORMULA_FUNCTIONS, HIGHLIGHT_NOTE_MESSAGE, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_PHOTO_FILES, MAX_PHOTO_ID_LENGTH, MAX_PHOTO_NAME_LENGTH, MAX_PHOTO_SIZE_MB, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, OTHER_MAX_LENGTH, PHOTO_FORMATS, PHOTO_TYPE, REQUIRED_MESSAGE, STRUCTURED_FIELD_TYPES, UPLOAD_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, createForm, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, getFieldType, isEmptyValue, isFormEvent, isIsoDate, isJson, isOtherValue, isPhotoFormat, isPhotoRef, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, optionsSource, otherOption, parseForm, parseFormula, parseTimestamp, photoCountMessage, photoFormatList, photoLimits, photoRefs, photoSizeMessage, photoTypeMessage, ratingDisplay, recall, registerFieldType, renameFormulaKey, replay, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, validateField, when, withHostOptions };
|
|
2445
2664
|
//# sourceMappingURL=index.js.map
|
|
2446
2665
|
//# sourceMappingURL=index.js.map
|