@marktiderman/genesis-forms 0.1.1 → 0.3.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 +121 -17
- package/conformance/{after-class-back-after-a-jump.scenario.json → event-feedback-back-after-a-jump.scenario.json} +21 -21
- package/conformance/{after-class-contact-yes-completed.scenario.json → event-feedback-contact-yes-completed.scenario.json} +21 -21
- package/conformance/{after-class-contact-yes-requires-name-and-email.scenario.json → event-feedback-contact-yes-requires-name-and-email.scenario.json} +19 -19
- package/conformance/{after-class-happy-path.scenario.json → event-feedback-happy-path.scenario.json} +24 -24
- package/conformance/{after-class-invalid-email-rejected.scenario.json → event-feedback-invalid-email-rejected.scenario.json} +19 -19
- package/conformance/{after-class-low-rating-skips-went-well.scenario.json → event-feedback-low-rating-skips-went-well.scenario.json} +21 -21
- package/conformance/{after-class-server-drops-unknown-and-hidden-answers.scenario.json → event-feedback-server-drops-unknown-and-hidden-answers.scenario.json} +22 -22
- package/conformance/{after-class-server-ignores-a-client-sent-coach.scenario.json → event-feedback-server-ignores-a-client-sent-owner.scenario.json} +24 -24
- package/conformance/{monthly-pulse-default-ending.scenario.json → monthly-check-in-default-ending.scenario.json} +6 -6
- package/conformance/{monthly-pulse-promoter-ending-via-jump.scenario.json → monthly-check-in-promoter-ending-via-jump.scenario.json} +6 -6
- package/dist/destinations.cjs +1211 -0
- package/dist/destinations.cjs.map +1 -0
- package/dist/destinations.d.cts +465 -0
- package/dist/destinations.d.ts +465 -0
- package/dist/destinations.js +1169 -0
- package/dist/destinations.js.map +1 -0
- package/dist/{engine-LsPRTzt3.d.cts → engine-CPLtKVew.d.cts} +7 -6
- package/dist/{engine-Cp5t2_Am.d.ts → engine-DOrPgFDw.d.ts} +7 -6
- package/dist/index.cjs +410 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -18
- package/dist/index.d.ts +122 -18
- package/dist/index.js +399 -25
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +350 -29
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +13 -12
- package/dist/server.d.ts +13 -12
- package/dist/server.js +350 -29
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +354 -29
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -3
- package/dist/testing.d.ts +4 -3
- package/dist/testing.js +354 -29
- package/dist/testing.js.map +1 -1
- package/dist/{validate-j8Z188mF.d.cts → types-BBKBQ8sp.d.cts} +9 -29
- package/dist/{validate-j8Z188mF.d.ts → types-BBKBQ8sp.d.ts} +9 -29
- package/dist/validate-BXXqi0fq.d.cts +30 -0
- package/dist/validate-BjNwF1UE.d.ts +30 -0
- package/package.json +14 -2
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,211 @@ function isFormEvent(value) {
|
|
|
62
62
|
return typeof value.field === "string" && (value.value === void 0 || isJson(value.value)) && (value.source === void 0 || value.source === "prefill");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// src/structured.ts
|
|
66
|
+
var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
67
|
+
function intProp(field2, name, fallback, floor = 1) {
|
|
68
|
+
const v = field2.props?.[name];
|
|
69
|
+
return typeof v === "number" && Number.isInteger(v) && v >= floor ? v : fallback;
|
|
70
|
+
}
|
|
71
|
+
function optionalIntProp(field2, name, floor = 0) {
|
|
72
|
+
const v = field2.props?.[name];
|
|
73
|
+
return typeof v === "number" && Number.isInteger(v) && v >= floor ? v : void 0;
|
|
74
|
+
}
|
|
75
|
+
var blank = (v) => typeof v !== "string" || v.trim() === "";
|
|
76
|
+
var blankText = (v) => typeof v === "string" && v.trim() === "";
|
|
77
|
+
var emptyList = (value, isBlank) => value === void 0 || value === null || Array.isArray(value) && value.every(isBlank);
|
|
78
|
+
var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
79
|
+
function listLimits(field2) {
|
|
80
|
+
return { min: optionalIntProp(field2, "min") ?? 0, max: intProp(field2, "max", 10), itemMaxLength: intProp(field2, "itemMaxLength", 500) };
|
|
81
|
+
}
|
|
82
|
+
var listItems = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.trim() !== "") : [];
|
|
83
|
+
var MAX_TABLE_COLUMNS = 6;
|
|
84
|
+
var MAX_TABLE_CELL_LENGTH = 2e3;
|
|
85
|
+
function tableColumns(field2) {
|
|
86
|
+
const raw = field2.props?.columns;
|
|
87
|
+
if (!Array.isArray(raw)) return [];
|
|
88
|
+
const seen = /* @__PURE__ */ new Set();
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const c of raw) {
|
|
91
|
+
if (!isObject(c) || typeof c.key !== "string" || !c.key || seen.has(c.key)) continue;
|
|
92
|
+
seen.add(c.key);
|
|
93
|
+
const column = { key: c.key, label: typeof c.label === "string" ? c.label : c.key };
|
|
94
|
+
if (c.long === true) column.long = true;
|
|
95
|
+
out.push(column);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function tableLimits(field2) {
|
|
100
|
+
return { minRows: optionalIntProp(field2, "minRows") ?? 0, maxRows: intProp(field2, "maxRows", 3) };
|
|
101
|
+
}
|
|
102
|
+
var filledRows = (value) => Array.isArray(value) ? value.filter((r) => isObject(r) && Object.values(r).some((c) => !blank(c))) : [];
|
|
103
|
+
var MAX_TIMESTAMP_SECONDS = 86400;
|
|
104
|
+
function formatTimestamp(seconds) {
|
|
105
|
+
const total = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;
|
|
106
|
+
const h = Math.floor(total / 3600);
|
|
107
|
+
const m = Math.floor(total % 3600 / 60);
|
|
108
|
+
const s = String(total % 60).padStart(2, "0");
|
|
109
|
+
return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
|
|
110
|
+
}
|
|
111
|
+
function parseTimestamp(text) {
|
|
112
|
+
const t = text.trim();
|
|
113
|
+
if (!t) return null;
|
|
114
|
+
const parts = t.split(":");
|
|
115
|
+
if (parts.length > 3) return null;
|
|
116
|
+
if (parts.length === 1) {
|
|
117
|
+
if (!/^\d+(\.\d+)?$/.test(t)) return null;
|
|
118
|
+
const n = Number(t);
|
|
119
|
+
return n <= MAX_TIMESTAMP_SECONDS ? n : null;
|
|
120
|
+
}
|
|
121
|
+
const last = parts.length - 1;
|
|
122
|
+
let total = 0;
|
|
123
|
+
for (let i = 0; i < parts.length; i++) {
|
|
124
|
+
const p = parts[i];
|
|
125
|
+
const ok = i === 0 ? /^\d+$/.test(p) : i === last ? /^\d{2}(\.\d+)?$/.test(p) : /^\d{2}$/.test(p);
|
|
126
|
+
if (!ok) return null;
|
|
127
|
+
const n = Number(p);
|
|
128
|
+
if (i > 0 && n >= 60) return null;
|
|
129
|
+
total = total * 60 + n;
|
|
130
|
+
}
|
|
131
|
+
return total <= MAX_TIMESTAMP_SECONDS ? total : null;
|
|
132
|
+
}
|
|
133
|
+
var isTimestamp = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= MAX_TIMESTAMP_SECONDS;
|
|
134
|
+
var notesWithText = (value) => Array.isArray(value) ? value.filter((n) => isObject(n) && !blank(n.text)) : [];
|
|
135
|
+
var MAX_HIGHLIGHT_NOTE_LENGTH = 1e3;
|
|
136
|
+
var MAX_HIGHLIGHT_QUOTE_LENGTH = 2e3;
|
|
137
|
+
var HIGHLIGHT_NOTE_MESSAGE = "Add a note to each highlight.";
|
|
138
|
+
var sourceText = (field2) => typeof field2.props?.text === "string" && field2.props.text ? field2.props.text : void 0;
|
|
139
|
+
var STRUCTURED_FIELD_TYPES = ["list", "table", "media-notes", "text-highlights"];
|
|
140
|
+
var structuredFieldTypes = [
|
|
141
|
+
{
|
|
142
|
+
type: "list",
|
|
143
|
+
label: "List",
|
|
144
|
+
category: "text",
|
|
145
|
+
value: { json: true },
|
|
146
|
+
// logic.ts `contains` on an array is exact item equality, same as multi-choice, so it is offered.
|
|
147
|
+
operators: ["contains", "answered", "empty"],
|
|
148
|
+
a11y: { semantics: "group" },
|
|
149
|
+
isEmpty: (value) => emptyList(value, blankText),
|
|
150
|
+
validate: (value, field2) => {
|
|
151
|
+
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) return "Enter a list of answers.";
|
|
152
|
+
const { min, max, itemMaxLength } = listLimits(field2);
|
|
153
|
+
if (value.some((v) => v.trim().length > itemMaxLength)) return `Keep each item under ${itemMaxLength} characters.`;
|
|
154
|
+
const count = listItems(value).length;
|
|
155
|
+
if (count < min) return `Add at least ${plural(min, "item")}.`;
|
|
156
|
+
if (count > max) return `Add at most ${plural(max, "item")}.`;
|
|
157
|
+
return null;
|
|
158
|
+
},
|
|
159
|
+
normalize: (value) => Array.isArray(value) ? listItems(value).map((v) => v.trim()) : value,
|
|
160
|
+
summarize: (value) => listItems(value).map((v) => v.trim()).join("; "),
|
|
161
|
+
samples: (field2) => {
|
|
162
|
+
const { min, max } = listLimits(field2);
|
|
163
|
+
return [Array.from({ length: Math.min(Math.max(1, min), max) }, (_, i) => `Item ${i + 1}`)];
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: "table",
|
|
168
|
+
label: "Table",
|
|
169
|
+
category: "advanced",
|
|
170
|
+
value: { json: true },
|
|
171
|
+
operators: ["answered", "empty"],
|
|
172
|
+
a11y: { semantics: "group" },
|
|
173
|
+
isEmpty: (value) => emptyList(value, (r) => isObject(r) && Object.values(r).every(blankText)),
|
|
174
|
+
validate: (value, field2) => {
|
|
175
|
+
const columns = tableColumns(field2);
|
|
176
|
+
if (!columns.length) return "This question has no columns.";
|
|
177
|
+
if (!Array.isArray(value) || !value.every(isObject)) return "Fill in the rows.";
|
|
178
|
+
const keys = new Set(columns.map((c) => c.key));
|
|
179
|
+
for (const row of value) {
|
|
180
|
+
for (const [key, cell] of Object.entries(row)) {
|
|
181
|
+
if (!keys.has(key)) return "An answer names a column this question doesn't have.";
|
|
182
|
+
if (typeof cell !== "string") return "Fill in the rows with text.";
|
|
183
|
+
if (cell.trim().length > MAX_TABLE_CELL_LENGTH) return `Keep each answer under ${MAX_TABLE_CELL_LENGTH} characters.`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const { minRows, maxRows } = tableLimits(field2);
|
|
187
|
+
const count = filledRows(value).length;
|
|
188
|
+
if (count < minRows) return `Fill in at least ${plural(minRows, "row")}.`;
|
|
189
|
+
if (count > maxRows) return `Fill in at most ${plural(maxRows, "row")}.`;
|
|
190
|
+
return null;
|
|
191
|
+
},
|
|
192
|
+
normalize: (value) => Array.isArray(value) ? filledRows(value).map((row) => {
|
|
193
|
+
const out = {};
|
|
194
|
+
for (const [key, cell] of Object.entries(row)) if (typeof cell === "string") out[key] = cell.trim();
|
|
195
|
+
return out;
|
|
196
|
+
}) : value,
|
|
197
|
+
summarize: (value, field2) => {
|
|
198
|
+
const columns = tableColumns(field2);
|
|
199
|
+
return filledRows(value).map((row) => columns.filter((c) => !blank(row[c.key])).map((c) => `${c.label}: ${row[c.key].trim()}`).join(" \xB7 ")).join(" / ");
|
|
200
|
+
},
|
|
201
|
+
samples: (field2) => {
|
|
202
|
+
const columns = tableColumns(field2);
|
|
203
|
+
if (!columns.length) return [];
|
|
204
|
+
const { minRows, maxRows } = tableLimits(field2);
|
|
205
|
+
const row = Object.fromEntries(columns.map((c) => [c.key, "sample"]));
|
|
206
|
+
return [Array.from({ length: Math.min(Math.max(1, minRows), maxRows) }, () => ({ ...row }))];
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: "media-notes",
|
|
211
|
+
label: "Notes at moments",
|
|
212
|
+
category: "media",
|
|
213
|
+
value: { json: true },
|
|
214
|
+
operators: ["answered", "empty"],
|
|
215
|
+
a11y: { semantics: "group" },
|
|
216
|
+
isEmpty: (value) => emptyList(value, (n) => isObject(n) && blankText(n.text)),
|
|
217
|
+
validate: (value, field2) => {
|
|
218
|
+
if (!Array.isArray(value)) return "Add notes.";
|
|
219
|
+
const textMaxLength = intProp(field2, "textMaxLength", 1e3);
|
|
220
|
+
for (const note of value) {
|
|
221
|
+
if (!isObject(note) || typeof note.text !== "string") return "Add notes.";
|
|
222
|
+
if (!isTimestamp(note.at)) return "Each note needs a time.";
|
|
223
|
+
if (note.text.trim().length > textMaxLength) return `Keep each note under ${textMaxLength} characters.`;
|
|
224
|
+
}
|
|
225
|
+
const maxNotes = intProp(field2, "maxNotes", 20);
|
|
226
|
+
return notesWithText(value).length > maxNotes ? `Add at most ${plural(maxNotes, "note")}.` : null;
|
|
227
|
+
},
|
|
228
|
+
normalize: (value) => Array.isArray(value) ? notesWithText(value).map((n) => ({ at: n.at, text: n.text.trim() })).sort((a, b) => a.at - b.at) : value,
|
|
229
|
+
summarize: (value) => notesWithText(value).map((n) => ({ at: typeof n.at === "number" ? n.at : 0, text: n.text.trim() })).sort((a, b) => a.at - b.at).map((n) => `${formatTimestamp(n.at)} ${n.text}`).join("; "),
|
|
230
|
+
samples: () => [[{ at: 0, text: "sample" }]]
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
type: "text-highlights",
|
|
234
|
+
label: "Highlights",
|
|
235
|
+
category: "media",
|
|
236
|
+
value: { json: true },
|
|
237
|
+
operators: ["answered", "empty"],
|
|
238
|
+
a11y: { semantics: "group" },
|
|
239
|
+
isEmpty: (value) => emptyList(value, () => false),
|
|
240
|
+
validate: (value, field2) => {
|
|
241
|
+
if (!Array.isArray(value)) return "Highlight a passage.";
|
|
242
|
+
const maxHighlights = intProp(field2, "maxHighlights", 20);
|
|
243
|
+
if (value.length > maxHighlights) return `Add at most ${plural(maxHighlights, "highlight")}.`;
|
|
244
|
+
const text = sourceText(field2);
|
|
245
|
+
for (const h of value) {
|
|
246
|
+
if (!isObject(h) || !Number.isInteger(h.start) || !Number.isInteger(h.end) || typeof h.quote !== "string" || typeof h.note !== "string") {
|
|
247
|
+
return "Highlight a passage of the text.";
|
|
248
|
+
}
|
|
249
|
+
const start = h.start;
|
|
250
|
+
const end = h.end;
|
|
251
|
+
if (start < 0 || end <= start || h.quote.length !== end - start) return "Highlight a passage of the text.";
|
|
252
|
+
if (end - start > MAX_HIGHLIGHT_QUOTE_LENGTH) return `Highlight under ${MAX_HIGHLIGHT_QUOTE_LENGTH} characters at a time.`;
|
|
253
|
+
if (text !== void 0 && (end > text.length || text.slice(start, end) !== h.quote)) return "A highlight doesn't match the text.";
|
|
254
|
+
if (h.note.trim() === "") return HIGHLIGHT_NOTE_MESSAGE;
|
|
255
|
+
if (h.note.trim().length > MAX_HIGHLIGHT_NOTE_LENGTH) return `Keep each note under ${MAX_HIGHLIGHT_NOTE_LENGTH} characters.`;
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
},
|
|
259
|
+
normalize: (value) => Array.isArray(value) ? value.filter(isObject).map((h) => ({ start: h.start, end: h.end, quote: h.quote, note: String(h.note).trim() })).sort((a, b) => a.start - b.start || a.end - b.end) : value,
|
|
260
|
+
summarize: (value) => Array.isArray(value) ? value.filter(isObject).map((h) => `"${String(h.quote)}" \u2014 ${String(h.note).trim()}`).join("; ") : "",
|
|
261
|
+
samples: (field2) => {
|
|
262
|
+
const text = sourceText(field2);
|
|
263
|
+
if (text === void 0) return [[{ start: 0, end: 6, quote: "sample", note: "sample" }]];
|
|
264
|
+
const end = Math.min(text.length, 20);
|
|
265
|
+
return [[{ start: 0, end, quote: text.slice(0, end), note: "sample" }]];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
];
|
|
269
|
+
|
|
65
270
|
// src/fields.ts
|
|
66
271
|
var REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/field-types");
|
|
67
272
|
var globalScope = globalThis;
|
|
@@ -249,7 +454,7 @@ var builtins = [
|
|
|
249
454
|
summarize: (value) => value ? "Yes" : "No"
|
|
250
455
|
}
|
|
251
456
|
];
|
|
252
|
-
for (const manifest of builtins) registerFieldType(manifest);
|
|
457
|
+
for (const manifest of [...builtins, ...structuredFieldTypes]) registerFieldType(manifest);
|
|
253
458
|
|
|
254
459
|
// src/logic.ts
|
|
255
460
|
function own(record, key) {
|
|
@@ -399,7 +604,31 @@ function recallRefs(text) {
|
|
|
399
604
|
}
|
|
400
605
|
|
|
401
606
|
// src/parse.ts
|
|
402
|
-
|
|
607
|
+
function shown(v) {
|
|
608
|
+
let text;
|
|
609
|
+
try {
|
|
610
|
+
text = JSON.stringify(v);
|
|
611
|
+
} catch {
|
|
612
|
+
text = void 0;
|
|
613
|
+
}
|
|
614
|
+
text ??= typeof v === "bigint" ? `${v}n` : typeof v;
|
|
615
|
+
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
616
|
+
}
|
|
617
|
+
var isObject2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
618
|
+
var isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
619
|
+
var LITERAL_TYPE = {
|
|
620
|
+
"short-text": "string",
|
|
621
|
+
"long-text": "string",
|
|
622
|
+
email: "string",
|
|
623
|
+
phone: "string",
|
|
624
|
+
rating: "number",
|
|
625
|
+
nps: "number",
|
|
626
|
+
"single-choice": "string",
|
|
627
|
+
"multi-choice": "string",
|
|
628
|
+
"yes-no": "boolean",
|
|
629
|
+
list: "string"
|
|
630
|
+
};
|
|
631
|
+
var ORDERING = /* @__PURE__ */ new Set(["lt", "lte", "gt", "gte"]);
|
|
403
632
|
var OPERATORS = /* @__PURE__ */ new Set([
|
|
404
633
|
"eq",
|
|
405
634
|
"neq",
|
|
@@ -439,7 +668,48 @@ function parseForm(input) {
|
|
|
439
668
|
}
|
|
440
669
|
return true;
|
|
441
670
|
};
|
|
442
|
-
|
|
671
|
+
const optionalInt = (v, path, floor) => {
|
|
672
|
+
if (v !== void 0 && !(isInt(v) && v >= floor)) issue(path, `Must be a whole number of ${floor} or more.`);
|
|
673
|
+
};
|
|
674
|
+
const ordered = (low, high, path, what) => {
|
|
675
|
+
if (isInt(low) && isInt(high) && low > high) issue(path, `Can't be more than the ${what}.`);
|
|
676
|
+
};
|
|
677
|
+
const structuredProps = (type, props2, path) => {
|
|
678
|
+
if (type === "list") {
|
|
679
|
+
optionalInt(props2.min, `${path}.min`, 0);
|
|
680
|
+
optionalInt(props2.max, `${path}.max`, 1);
|
|
681
|
+
optionalInt(props2.itemMaxLength, `${path}.itemMaxLength`, 1);
|
|
682
|
+
optionalText(props2.placeholder, `${path}.placeholder`);
|
|
683
|
+
ordered(props2.min, props2.max ?? 10, `${path}.min`, "most items");
|
|
684
|
+
} else if (type === "table") {
|
|
685
|
+
const columns = props2.columns;
|
|
686
|
+
if (!Array.isArray(columns) || columns.length < 1 || columns.length > MAX_TABLE_COLUMNS) {
|
|
687
|
+
issue(`${path}.columns`, `Must be a list of 1 to ${MAX_TABLE_COLUMNS} columns.`);
|
|
688
|
+
} else {
|
|
689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
690
|
+
columns.forEach((c, j) => {
|
|
691
|
+
const at = `${path}.columns[${j}]`;
|
|
692
|
+
if (!isObject2(c)) return issue(at, "Each column needs a key and a label.");
|
|
693
|
+
if (name(c.key, `${at}.key`, "A column key")) {
|
|
694
|
+
if (seen.has(c.key)) issue(`${at}.key`, `Duplicate column key "${c.key}".`);
|
|
695
|
+
seen.add(c.key);
|
|
696
|
+
}
|
|
697
|
+
text(c.label, `${at}.label`);
|
|
698
|
+
optionalBoolean(c.long, `${at}.long`);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
optionalInt(props2.minRows, `${path}.minRows`, 0);
|
|
702
|
+
optionalInt(props2.maxRows, `${path}.maxRows`, 1);
|
|
703
|
+
ordered(props2.minRows, props2.maxRows ?? 3, `${path}.minRows`, "most rows");
|
|
704
|
+
} else if (type === "media-notes") {
|
|
705
|
+
optionalInt(props2.maxNotes, `${path}.maxNotes`, 1);
|
|
706
|
+
optionalInt(props2.textMaxLength, `${path}.textMaxLength`, 1);
|
|
707
|
+
} else if (type === "text-highlights") {
|
|
708
|
+
optionalText(props2.text, `${path}.text`);
|
|
709
|
+
optionalInt(props2.maxHighlights, `${path}.maxHighlights`, 1);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
if (!isObject2(input)) {
|
|
443
713
|
issue("", "A form definition must be an object.");
|
|
444
714
|
return fail();
|
|
445
715
|
}
|
|
@@ -448,19 +718,19 @@ function parseForm(input) {
|
|
|
448
718
|
text(input.title, "title");
|
|
449
719
|
optionalText(input.description, "description");
|
|
450
720
|
if (input.layout !== void 0) {
|
|
451
|
-
if (!
|
|
721
|
+
if (!isObject2(input.layout)) issue("layout", "Must be an object.");
|
|
452
722
|
else if (input.layout.mode !== "focus" && input.layout.mode !== "classic") {
|
|
453
723
|
issue("layout.mode", "Must be focus or classic.");
|
|
454
724
|
}
|
|
455
725
|
}
|
|
456
726
|
if (input.settings !== void 0) {
|
|
457
|
-
if (!
|
|
727
|
+
if (!isObject2(input.settings)) issue("settings", "Must be an object.");
|
|
458
728
|
else {
|
|
459
729
|
optionalBoolean(input.settings.anonymous, "settings.anonymous");
|
|
460
730
|
optionalBoolean(input.settings.clearHiddenValues, "settings.clearHiddenValues");
|
|
461
731
|
}
|
|
462
732
|
}
|
|
463
|
-
if (input.meta !== void 0 && (!
|
|
733
|
+
if (input.meta !== void 0 && (!isObject2(input.meta) || !isJson(input.meta))) {
|
|
464
734
|
issue("meta", "Must be a JSON object.");
|
|
465
735
|
}
|
|
466
736
|
if (!Array.isArray(input.blocks)) {
|
|
@@ -472,9 +742,10 @@ function parseForm(input) {
|
|
|
472
742
|
const keys = /* @__PURE__ */ new Map();
|
|
473
743
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
474
744
|
const order = /* @__PURE__ */ new Map();
|
|
745
|
+
const choices = /* @__PURE__ */ new Map();
|
|
475
746
|
input.blocks.forEach((block, i) => {
|
|
476
747
|
const path = `blocks[${i}]`;
|
|
477
|
-
if (!
|
|
748
|
+
if (!isObject2(block) || typeof block.id !== "string" || !block.id) {
|
|
478
749
|
issue(path, "Every block needs a string id.");
|
|
479
750
|
return;
|
|
480
751
|
}
|
|
@@ -495,18 +766,30 @@ function parseForm(input) {
|
|
|
495
766
|
issue(`${path}.type`, `Unknown field type "${String(block.type)}".`);
|
|
496
767
|
}
|
|
497
768
|
if (block.props !== void 0) {
|
|
498
|
-
if (!
|
|
769
|
+
if (!isObject2(block.props) || !isJson(block.props)) issue(`${path}.props`, "Must be a JSON object.");
|
|
499
770
|
else if (block.props.options !== void 0) {
|
|
500
771
|
const options = block.props.options;
|
|
501
772
|
if (!Array.isArray(options)) issue(`${path}.props.options`, "Must be an array.");
|
|
502
773
|
else {
|
|
503
774
|
options.forEach((o, j) => {
|
|
504
775
|
const at = `${path}.props.options[${j}]`;
|
|
505
|
-
if (!
|
|
776
|
+
if (!isObject2(o) || typeof o.value !== "string") issue(at, "Each option needs a string value.");
|
|
506
777
|
else optionalText(o.label, `${at}.label`);
|
|
507
778
|
});
|
|
779
|
+
choices.set(
|
|
780
|
+
block.id,
|
|
781
|
+
new Set(options.flatMap((o) => isObject2(o) && typeof o.value === "string" ? [o.value] : []))
|
|
782
|
+
);
|
|
508
783
|
}
|
|
509
784
|
}
|
|
785
|
+
const { max, maxLength: maxLength2 } = isObject2(block.props) ? block.props : {};
|
|
786
|
+
if (block.type === "rating" && max !== void 0 && !(isInt(max) && max >= 2 && max <= 10)) {
|
|
787
|
+
issue(`${path}.props.max`, "Must be a whole number from 2 to 10.");
|
|
788
|
+
}
|
|
789
|
+
if (maxLength2 !== void 0 && !(isInt(maxLength2) && maxLength2 > 0)) {
|
|
790
|
+
issue(`${path}.props.maxLength`, "Must be a whole number above 0.");
|
|
791
|
+
}
|
|
792
|
+
if (isObject2(block.props) && typeof block.type === "string") structuredProps(block.type, block.props, `${path}.props`);
|
|
510
793
|
}
|
|
511
794
|
if (validKey) {
|
|
512
795
|
if (keys.has(block.key)) issue(`${path}.key`, `Duplicate key "${String(block.key)}".`);
|
|
@@ -523,9 +806,10 @@ function parseForm(input) {
|
|
|
523
806
|
}
|
|
524
807
|
});
|
|
525
808
|
const hiddenNames = /* @__PURE__ */ new Set();
|
|
809
|
+
const hiddenTypes = /* @__PURE__ */ new Map();
|
|
526
810
|
if (input.hidden !== void 0 && !Array.isArray(input.hidden)) issue("hidden", "Must be an array.");
|
|
527
811
|
(Array.isArray(input.hidden) ? input.hidden : []).forEach((h, i) => {
|
|
528
|
-
if (!
|
|
812
|
+
if (!isObject2(h) || typeof h.name !== "string" || !h.name) {
|
|
529
813
|
issue(`hidden[${i}]`, "Each hidden input needs a name.");
|
|
530
814
|
return;
|
|
531
815
|
}
|
|
@@ -538,13 +822,15 @@ function parseForm(input) {
|
|
|
538
822
|
issue(`hidden[${i}].type`, "Must be string, number or boolean.");
|
|
539
823
|
}
|
|
540
824
|
optionalBoolean(h.sensitive, `hidden[${i}].sensitive`);
|
|
825
|
+
optionalBoolean(h.identifying, `hidden[${i}].identifying`);
|
|
541
826
|
hiddenNames.add(h.name);
|
|
827
|
+
if (typeof h.type === "string") hiddenTypes.set(h.name, h.type);
|
|
542
828
|
});
|
|
543
829
|
const endingIds = /* @__PURE__ */ new Set();
|
|
544
830
|
if (input.endings !== void 0 && !Array.isArray(input.endings)) issue("endings", "Must be an array.");
|
|
545
831
|
(Array.isArray(input.endings) ? input.endings : []).forEach((e, i) => {
|
|
546
832
|
const path = `endings[${i}]`;
|
|
547
|
-
if (!
|
|
833
|
+
if (!isObject2(e)) {
|
|
548
834
|
issue(path, "Each ending must be an object with an id and a title.");
|
|
549
835
|
return;
|
|
550
836
|
}
|
|
@@ -556,7 +842,7 @@ function parseForm(input) {
|
|
|
556
842
|
});
|
|
557
843
|
const checkOperand = (o, path, reads) => {
|
|
558
844
|
if (o === void 0) return;
|
|
559
|
-
if (!
|
|
845
|
+
if (!isObject2(o)) return issue(path, "An operand must be an object.");
|
|
560
846
|
if ("field" in o) {
|
|
561
847
|
if (typeof o.field !== "string" || !fieldTypes.has(o.field)) return issue(path, `No field "${String(o.field)}".`);
|
|
562
848
|
reads.push(o.field);
|
|
@@ -569,7 +855,7 @@ function parseForm(input) {
|
|
|
569
855
|
}
|
|
570
856
|
};
|
|
571
857
|
const checkCondition = (c, path, reads, depth = 0) => {
|
|
572
|
-
if (!
|
|
858
|
+
if (!isObject2(c)) return issue(path, "A condition must be an object.");
|
|
573
859
|
if (depth > MAX_CONDITION_DEPTH) return issue(path, "The condition is nested too deeply.");
|
|
574
860
|
if ("all" in c || "any" in c) {
|
|
575
861
|
const group = "all" in c ? "all" : "any";
|
|
@@ -578,7 +864,7 @@ function parseForm(input) {
|
|
|
578
864
|
return list.forEach((x, i) => checkCondition(x, `${path}.${group}[${i}]`, reads, depth + 1));
|
|
579
865
|
}
|
|
580
866
|
if ("not" in c) return checkCondition(c.not, `${path}.not`, reads, depth + 1);
|
|
581
|
-
if (typeof c.op !== "string" || !
|
|
867
|
+
if (typeof c.op !== "string" || !isObject2(c.left)) {
|
|
582
868
|
return issue(path, "A comparison needs an `op` and a `left` operand.");
|
|
583
869
|
}
|
|
584
870
|
if (!OPERATORS.has(c.op)) issue(`${path}.op`, `Unknown operator "${c.op}".`);
|
|
@@ -587,16 +873,53 @@ function parseForm(input) {
|
|
|
587
873
|
if (typeof c.left.field === "string") {
|
|
588
874
|
const manifest = getFieldType(fieldTypes.get(c.left.field) ?? "");
|
|
589
875
|
if (manifest && OPERATORS.has(c.op) && !manifest.operators.includes(c.op)) {
|
|
590
|
-
issue(`${path}.op`, `"${c.op}" does not apply to a ${manifest.type} field.`);
|
|
876
|
+
return issue(`${path}.op`, `"${c.op}" does not apply to a ${manifest.type} field.`);
|
|
591
877
|
}
|
|
592
878
|
}
|
|
879
|
+
checkLiterals(c.op, c.left, c.right, path);
|
|
880
|
+
};
|
|
881
|
+
const checkLiterals = (op, left, right, path) => {
|
|
882
|
+
if (op === "answered" || op === "empty" || !OPERATORS.has(op)) return;
|
|
883
|
+
const literal = isObject2(right) && "value" in right;
|
|
884
|
+
if (right !== void 0 && !literal) return;
|
|
885
|
+
let what = "the left side";
|
|
886
|
+
let type;
|
|
887
|
+
let options;
|
|
888
|
+
if (typeof left.field === "string" && fieldTypes.has(left.field)) {
|
|
889
|
+
const fieldType = fieldTypes.get(left.field);
|
|
890
|
+
what = `"${keyOf(left.field)}"`;
|
|
891
|
+
type = LITERAL_TYPE[fieldType];
|
|
892
|
+
if (fieldType === "single-choice" || fieldType === "multi-choice") options = choices.get(left.field);
|
|
893
|
+
} else if (typeof left.hidden === "string" && hiddenTypes.has(left.hidden)) {
|
|
894
|
+
what = `hidden input "${left.hidden}"`;
|
|
895
|
+
type = hiddenTypes.get(left.hidden);
|
|
896
|
+
}
|
|
897
|
+
const outcome = op === "neq" ? "always true" : "never true";
|
|
898
|
+
let values;
|
|
899
|
+
if (op === "in") {
|
|
900
|
+
if (!literal || !Array.isArray(right.value)) {
|
|
901
|
+
return void warnings.push({ path: `${path}.right`, message: "`in` needs a list of values; it never matches." });
|
|
902
|
+
}
|
|
903
|
+
values = right.value;
|
|
904
|
+
} else if (!literal) return;
|
|
905
|
+
else values = [right.value];
|
|
906
|
+
if (ORDERING.has(op)) {
|
|
907
|
+
if (type !== void 0 && type !== "number") {
|
|
908
|
+
return void warnings.push({ path: `${path}.op`, message: `"${op}" compares numbers, but ${what} holds a ${type}; it is never true.` });
|
|
909
|
+
}
|
|
910
|
+
type = "number";
|
|
911
|
+
}
|
|
912
|
+
for (const v of values) {
|
|
913
|
+
const message = type !== void 0 && typeof v !== type ? `${what} holds a ${type}, but ${shown(v)} is not one; this comparison is ${outcome}.` : options && typeof v === "string" && !options.has(v) ? `${shown(v)} is not an option of ${what}; this comparison is ${outcome}.` : null;
|
|
914
|
+
if (message) warnings.push({ path: `${path}.right`, message });
|
|
915
|
+
}
|
|
593
916
|
};
|
|
594
917
|
const keyOf = (id) => [...keys].find(([, v]) => v === id)?.[0] ?? id;
|
|
595
918
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
596
919
|
if (input.logic !== void 0 && !Array.isArray(input.logic)) issue("logic", "Must be an array.");
|
|
597
920
|
(Array.isArray(input.logic) ? input.logic : []).forEach((rule, i) => {
|
|
598
921
|
const path = `logic[${i}]`;
|
|
599
|
-
if (!
|
|
922
|
+
if (!isObject2(rule) || typeof rule.id !== "string" || !isObject2(rule.when)) {
|
|
600
923
|
issue(path, "Each rule needs an id and a `when` condition.");
|
|
601
924
|
return;
|
|
602
925
|
}
|
|
@@ -615,7 +938,7 @@ function parseForm(input) {
|
|
|
615
938
|
at = rule.from;
|
|
616
939
|
inclusive = true;
|
|
617
940
|
}
|
|
618
|
-
if (
|
|
941
|
+
if (isObject2(rule.to)) {
|
|
619
942
|
if (typeof rule.to.ending !== "string" || !endingIds.has(rule.to.ending)) {
|
|
620
943
|
issue(`${path}.to`, `No ending "${String(rule.to.ending)}".`);
|
|
621
944
|
}
|
|
@@ -647,7 +970,7 @@ function parseForm(input) {
|
|
|
647
970
|
}
|
|
648
971
|
});
|
|
649
972
|
input.blocks.forEach((block, i) => {
|
|
650
|
-
if (!
|
|
973
|
+
if (!isObject2(block)) return;
|
|
651
974
|
const raw = block.kind === "field" ? [block.label, block.description] : block.kind === "content" ? [block.text] : [];
|
|
652
975
|
const texts = raw.filter((t) => typeof t === "string");
|
|
653
976
|
for (const t of texts) {
|
|
@@ -665,11 +988,23 @@ function parseForm(input) {
|
|
|
665
988
|
}
|
|
666
989
|
}
|
|
667
990
|
});
|
|
991
|
+
const extra = [["description", input.description]];
|
|
992
|
+
(Array.isArray(input.endings) ? input.endings : []).forEach((e, i) => {
|
|
993
|
+
if (isObject2(e)) extra.push([`endings[${i}]`, e.title], [`endings[${i}]`, e.description]);
|
|
994
|
+
});
|
|
995
|
+
for (const [path, t] of extra) {
|
|
996
|
+
if (typeof t !== "string") continue;
|
|
997
|
+
for (const ref of recallRefs(t)) {
|
|
998
|
+
if (ref.kind === "hidden" ? !hiddenNames.has(ref.name) : !keys.has(ref.name)) {
|
|
999
|
+
warnings.push({ path, message: `Recalls unknown ${ref.kind === "hidden" ? "hidden input" : "field"} "${ref.name}"; it shows its fallback text.` });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
668
1003
|
return issues.length ? fail() : { ok: true, form: input, warnings };
|
|
669
1004
|
}
|
|
670
1005
|
|
|
671
1006
|
// src/define.ts
|
|
672
|
-
function field(type, key, options,
|
|
1007
|
+
function field(type, key, options, props2) {
|
|
673
1008
|
const block = {
|
|
674
1009
|
kind: "field",
|
|
675
1010
|
id: options.id ?? key,
|
|
@@ -682,16 +1017,18 @@ function field(type, key, options, props) {
|
|
|
682
1017
|
if (options.required) block.required = true;
|
|
683
1018
|
if (options.sensitive) block.sensitive = true;
|
|
684
1019
|
if (options.identifying) block.identifying = true;
|
|
685
|
-
if (
|
|
1020
|
+
if (props2 && Object.keys(props2).length) block.props = props2;
|
|
686
1021
|
return block;
|
|
687
1022
|
}
|
|
1023
|
+
var props = (all) => Object.fromEntries(Object.entries(all).filter((e) => e[1] !== void 0));
|
|
688
1024
|
var choiceProps = (options) => ({
|
|
689
1025
|
options: options.map((o) => o.label ? { value: o.value, label: o.label } : { value: o.value })
|
|
690
1026
|
});
|
|
691
1027
|
var hiddenSpec = (type, o) => ({
|
|
692
1028
|
type,
|
|
693
1029
|
source: o.source ?? "url",
|
|
694
|
-
...o.sensitive ? { sensitive: true } : {}
|
|
1030
|
+
...o.sensitive ? { sensitive: true } : {},
|
|
1031
|
+
...o.identifying ? { identifying: true } : {}
|
|
695
1032
|
});
|
|
696
1033
|
var f = {
|
|
697
1034
|
shortText: (key, o) => field("short-text", key, o, o.maxLength ? { maxLength: o.maxLength } : void 0),
|
|
@@ -703,6 +1040,41 @@ var f = {
|
|
|
703
1040
|
singleChoice: (key, o) => field("single-choice", key, o, choiceProps(o.options)),
|
|
704
1041
|
multiChoice: (key, o) => field("multi-choice", key, o, choiceProps(o.options)),
|
|
705
1042
|
yesNo: (key, o) => field("yes-no", key, o),
|
|
1043
|
+
/** Several short answers, one per line: goals, action items, three things that went well. */
|
|
1044
|
+
list: (key, o) => field(
|
|
1045
|
+
"list",
|
|
1046
|
+
key,
|
|
1047
|
+
o,
|
|
1048
|
+
props({ min: o.min, max: o.max, itemMaxLength: o.itemMaxLength, placeholder: o.itemPlaceholder })
|
|
1049
|
+
),
|
|
1050
|
+
/**
|
|
1051
|
+
* A small table each answer fills row by row, one answer per column: an
|
|
1052
|
+
* action plan (What / Who / By when), pros and cons.
|
|
1053
|
+
*/
|
|
1054
|
+
table: (key, o) => field("table", key, o, {
|
|
1055
|
+
columns: o.columns.map((c) => c.long ? { key: c.key, label: c.label, long: true } : { key: c.key, label: c.label }),
|
|
1056
|
+
...props({ minRows: o.minRows, maxRows: o.maxRows })
|
|
1057
|
+
}),
|
|
1058
|
+
/**
|
|
1059
|
+
* Notes pinned to times in a video or audio (lecture notes, a recorded call
|
|
1060
|
+
* review): `at` is seconds from its start.
|
|
1061
|
+
*/
|
|
1062
|
+
mediaNotes: (key, o) => field(
|
|
1063
|
+
"media-notes",
|
|
1064
|
+
key,
|
|
1065
|
+
o,
|
|
1066
|
+
props({ maxNotes: o.maxNotes, textMaxLength: o.textMaxLength })
|
|
1067
|
+
),
|
|
1068
|
+
/**
|
|
1069
|
+
* Highlight passages of a text and note on each (annotating an article, a
|
|
1070
|
+
* policy or a contract): `text` here, or supplied by the host app.
|
|
1071
|
+
*/
|
|
1072
|
+
textHighlights: (key, o) => field(
|
|
1073
|
+
"text-highlights",
|
|
1074
|
+
key,
|
|
1075
|
+
o,
|
|
1076
|
+
props({ text: o.text, maxHighlights: o.maxHighlights })
|
|
1077
|
+
),
|
|
706
1078
|
/** Any registered field type, including plugins. */
|
|
707
1079
|
custom: (type, key, o) => field(type, key, o, o.props),
|
|
708
1080
|
heading: (id, text) => ({ kind: "content", id, type: "heading", text }),
|
|
@@ -786,7 +1158,8 @@ function defineForm(input) {
|
|
|
786
1158
|
name,
|
|
787
1159
|
type: spec.type,
|
|
788
1160
|
source: spec.source,
|
|
789
|
-
...spec.sensitive ? { sensitive: true } : {}
|
|
1161
|
+
...spec.sensitive ? { sensitive: true } : {},
|
|
1162
|
+
...spec.identifying ? { identifying: true } : {}
|
|
790
1163
|
}));
|
|
791
1164
|
}
|
|
792
1165
|
if (input.logic?.length) definition.logic = input.logic.map((rule, i) => rule.build(resolve2, `r${i + 1}`));
|
|
@@ -811,6 +1184,7 @@ ${list}`);
|
|
|
811
1184
|
|
|
812
1185
|
// src/hidden.ts
|
|
813
1186
|
var MAX_HIDDEN_STRING_LENGTH = 1e3;
|
|
1187
|
+
var DECIMAL = /^-?\d+(\.\d+)?$/;
|
|
814
1188
|
function coerceHiddenValue(value, type) {
|
|
815
1189
|
if (typeof value === "string" && value.length > MAX_HIDDEN_STRING_LENGTH) return void 0;
|
|
816
1190
|
if (type === "string") {
|
|
@@ -820,7 +1194,7 @@ function coerceHiddenValue(value, type) {
|
|
|
820
1194
|
}
|
|
821
1195
|
if (type === "number") {
|
|
822
1196
|
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
823
|
-
if (typeof value !== "string" ||
|
|
1197
|
+
if (typeof value !== "string" || !DECIMAL.test(value)) return void 0;
|
|
824
1198
|
const n = Number(value);
|
|
825
1199
|
return Number.isFinite(n) ? n : void 0;
|
|
826
1200
|
}
|
|
@@ -1001,7 +1375,7 @@ function build(definition, options, replaying = false) {
|
|
|
1001
1375
|
for (const id of state.path) {
|
|
1002
1376
|
const field2 = fields.get(id);
|
|
1003
1377
|
const value = own(state.values, id);
|
|
1004
|
-
if (field2 && value
|
|
1378
|
+
if (field2 && !isEmptyValue(value, field2)) out[id] = normalizeValue(value, field2);
|
|
1005
1379
|
}
|
|
1006
1380
|
return out;
|
|
1007
1381
|
}
|
|
@@ -1171,8 +1545,15 @@ function describeCondition(condition, def, fields = fieldMap(def)) {
|
|
|
1171
1545
|
return `${left} ${op} ${right}`;
|
|
1172
1546
|
}
|
|
1173
1547
|
|
|
1548
|
+
exports.HIGHLIGHT_NOTE_MESSAGE = HIGHLIGHT_NOTE_MESSAGE;
|
|
1174
1549
|
exports.MAX_HIDDEN_STRING_LENGTH = MAX_HIDDEN_STRING_LENGTH;
|
|
1550
|
+
exports.MAX_HIGHLIGHT_NOTE_LENGTH = MAX_HIGHLIGHT_NOTE_LENGTH;
|
|
1551
|
+
exports.MAX_HIGHLIGHT_QUOTE_LENGTH = MAX_HIGHLIGHT_QUOTE_LENGTH;
|
|
1552
|
+
exports.MAX_TABLE_CELL_LENGTH = MAX_TABLE_CELL_LENGTH;
|
|
1553
|
+
exports.MAX_TABLE_COLUMNS = MAX_TABLE_COLUMNS;
|
|
1554
|
+
exports.MAX_TIMESTAMP_SECONDS = MAX_TIMESTAMP_SECONDS;
|
|
1175
1555
|
exports.REQUIRED_MESSAGE = REQUIRED_MESSAGE;
|
|
1556
|
+
exports.STRUCTURED_FIELD_TYPES = STRUCTURED_FIELD_TYPES;
|
|
1176
1557
|
exports.canonicalJson = canonicalJson;
|
|
1177
1558
|
exports.choiceOptions = choiceOptions;
|
|
1178
1559
|
exports.coerceHidden = coerceHidden;
|
|
@@ -1185,20 +1566,25 @@ exports.evaluate = evaluate;
|
|
|
1185
1566
|
exports.explain = explain;
|
|
1186
1567
|
exports.f = f;
|
|
1187
1568
|
exports.fingerprint = fingerprint;
|
|
1569
|
+
exports.formatTimestamp = formatTimestamp;
|
|
1188
1570
|
exports.getFieldType = getFieldType;
|
|
1189
1571
|
exports.isEmptyValue = isEmptyValue;
|
|
1190
1572
|
exports.isFormEvent = isFormEvent;
|
|
1191
1573
|
exports.isJson = isJson;
|
|
1192
1574
|
exports.isSensitive = isSensitive;
|
|
1193
1575
|
exports.listFieldTypes = listFieldTypes;
|
|
1576
|
+
exports.listLimits = listLimits;
|
|
1194
1577
|
exports.normalizeValue = normalizeValue;
|
|
1195
1578
|
exports.parseForm = parseForm;
|
|
1579
|
+
exports.parseTimestamp = parseTimestamp;
|
|
1196
1580
|
exports.recall = recall;
|
|
1197
1581
|
exports.registerFieldType = registerFieldType;
|
|
1198
1582
|
exports.replay = replay;
|
|
1199
1583
|
exports.ruleVisible = ruleVisible;
|
|
1200
1584
|
exports.steps = steps;
|
|
1201
1585
|
exports.summarizeValue = summarizeValue;
|
|
1586
|
+
exports.tableColumns = tableColumns;
|
|
1587
|
+
exports.tableLimits = tableLimits;
|
|
1202
1588
|
exports.validateField = validateField;
|
|
1203
1589
|
exports.when = when;
|
|
1204
1590
|
//# sourceMappingURL=index.cjs.map
|