@automate.ax/catalog 0.153.1 → 0.154.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/form-fields.d.ts +434 -0
- package/dist/form-fields.js +341 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/triggers/core-dashboard.d.ts +20 -345
- package/dist/triggers/core-dashboard.js +3 -240
- package/dist/triggers/core.d.ts +1 -1
- package/dist/triggers/core.js +1 -1
- package/dist/triggers/index.d.ts +1 -1
- package/dist/triggers/index.js +1 -1
- package/package.json +1 -1
- package/src/form-fields.ts +513 -0
- package/src/index.ts +13 -5
- package/src/triggers/core-dashboard.ts +4 -395
- package/src/triggers/core.ts +0 -5
- package/src/triggers/index.ts +0 -5
package/package.json
CHANGED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import * as z from "zod/mini"
|
|
2
|
+
import { sentenceCase } from "./triggers/name"
|
|
3
|
+
|
|
4
|
+
const EMAIL_SCHEMA = z.email()
|
|
5
|
+
const URL_SCHEMA = z.url()
|
|
6
|
+
const DATE_SCHEMA = z.iso.date()
|
|
7
|
+
const DATETIME_SCHEMA = z.iso.datetime({ local: true })
|
|
8
|
+
const FIELD_NAME_SCHEMA = z
|
|
9
|
+
.string()
|
|
10
|
+
.check(z.trim(), z.minLength(1), z.maxLength(100))
|
|
11
|
+
const FIELD_LABEL_SCHEMA = z
|
|
12
|
+
.string()
|
|
13
|
+
.check(z.trim(), z.minLength(1), z.maxLength(255))
|
|
14
|
+
const FIELD_DESCRIPTION_SCHEMA = z.string().check(z.maxLength(2_000))
|
|
15
|
+
const FIELD_LENGTH_SCHEMA = z.int().check(z.nonnegative())
|
|
16
|
+
const FORM_FIELD_BASE_SCHEMA = {
|
|
17
|
+
description: z.optional(FIELD_DESCRIPTION_SCHEMA),
|
|
18
|
+
label: z.optional(FIELD_LABEL_SCHEMA),
|
|
19
|
+
name: FIELD_NAME_SCHEMA,
|
|
20
|
+
}
|
|
21
|
+
const FORM_TEXT_FIELD_SCHEMA = z.object({
|
|
22
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
23
|
+
defaultValue: z.optional(z.string()),
|
|
24
|
+
maxLength: z.optional(FIELD_LENGTH_SCHEMA),
|
|
25
|
+
minLength: z.optional(FIELD_LENGTH_SCHEMA),
|
|
26
|
+
placeholder: z.optional(z.string()),
|
|
27
|
+
required: z.prefault(z.boolean(), true),
|
|
28
|
+
type: z.enum(["email", "phone", "text", "textarea", "url"]),
|
|
29
|
+
})
|
|
30
|
+
const FORM_NUMBER_FIELD_SCHEMA = z.object({
|
|
31
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
32
|
+
defaultValue: z.optional(z.number()),
|
|
33
|
+
max: z.optional(z.number()),
|
|
34
|
+
min: z.optional(z.number()),
|
|
35
|
+
placeholder: z.optional(z.string()),
|
|
36
|
+
required: z.prefault(z.boolean(), true),
|
|
37
|
+
step: z.prefault(z.number().check(z.positive()), 1),
|
|
38
|
+
type: z.literal("number"),
|
|
39
|
+
})
|
|
40
|
+
const FORM_DATE_FIELD_SCHEMA = z.object({
|
|
41
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
42
|
+
defaultValue: z.optional(z.iso.date()),
|
|
43
|
+
max: z.optional(z.iso.date()),
|
|
44
|
+
min: z.optional(z.iso.date()),
|
|
45
|
+
required: z.prefault(z.boolean(), true),
|
|
46
|
+
step: z.prefault(z.int().check(z.positive()), 1),
|
|
47
|
+
type: z.literal("date"),
|
|
48
|
+
})
|
|
49
|
+
const FORM_DATETIME_FIELD_SCHEMA = z.object({
|
|
50
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
51
|
+
defaultValue: z.optional(z.iso.datetime({ local: true })),
|
|
52
|
+
max: z.optional(z.iso.datetime({ local: true })),
|
|
53
|
+
min: z.optional(z.iso.datetime({ local: true })),
|
|
54
|
+
required: z.prefault(z.boolean(), true),
|
|
55
|
+
step: z.prefault(z.number().check(z.positive()), 60),
|
|
56
|
+
type: z.literal("datetime"),
|
|
57
|
+
})
|
|
58
|
+
const FORM_CHOICE_FIELD_SCHEMA = {
|
|
59
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
60
|
+
defaultValue: z.optional(FIELD_NAME_SCHEMA),
|
|
61
|
+
options: z
|
|
62
|
+
.array(
|
|
63
|
+
z.object({
|
|
64
|
+
label: FIELD_LABEL_SCHEMA,
|
|
65
|
+
value: FIELD_NAME_SCHEMA,
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
68
|
+
.check(z.minLength(1)),
|
|
69
|
+
placeholder: z.optional(z.string()),
|
|
70
|
+
required: z.prefault(z.boolean(), true),
|
|
71
|
+
}
|
|
72
|
+
const FORM_SELECT_FIELD_SCHEMA = z.object({
|
|
73
|
+
...FORM_CHOICE_FIELD_SCHEMA,
|
|
74
|
+
type: z.literal("select"),
|
|
75
|
+
})
|
|
76
|
+
const FORM_RADIO_FIELD_SCHEMA = z.object({
|
|
77
|
+
...FORM_CHOICE_FIELD_SCHEMA,
|
|
78
|
+
type: z.literal("radio"),
|
|
79
|
+
})
|
|
80
|
+
const FORM_MULTI_SELECT_FIELD_SCHEMA = z.object({
|
|
81
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
82
|
+
defaultValue: z.optional(z.array(FIELD_NAME_SCHEMA)),
|
|
83
|
+
options: z
|
|
84
|
+
.array(
|
|
85
|
+
z.object({
|
|
86
|
+
label: FIELD_LABEL_SCHEMA,
|
|
87
|
+
value: FIELD_NAME_SCHEMA,
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
.check(z.minLength(1)),
|
|
91
|
+
placeholder: z.optional(z.string()),
|
|
92
|
+
required: z.prefault(z.boolean(), true),
|
|
93
|
+
type: z.literal("multi-select"),
|
|
94
|
+
})
|
|
95
|
+
const FORM_CHECKBOX_FIELD_SCHEMA = z.object({
|
|
96
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
97
|
+
defaultValue: z.prefault(z.boolean(), false),
|
|
98
|
+
required: z.prefault(z.boolean(), false),
|
|
99
|
+
type: z.literal("checkbox"),
|
|
100
|
+
})
|
|
101
|
+
const FORM_FILE_FIELD_SCHEMA = z.object({
|
|
102
|
+
...FORM_FIELD_BASE_SCHEMA,
|
|
103
|
+
required: z.prefault(z.boolean(), true),
|
|
104
|
+
type: z.literal("file"),
|
|
105
|
+
})
|
|
106
|
+
const FORM_FIELD_SCHEMA = z.pipe(
|
|
107
|
+
z.discriminatedUnion("type", [
|
|
108
|
+
FORM_TEXT_FIELD_SCHEMA,
|
|
109
|
+
FORM_NUMBER_FIELD_SCHEMA,
|
|
110
|
+
FORM_DATE_FIELD_SCHEMA,
|
|
111
|
+
FORM_DATETIME_FIELD_SCHEMA,
|
|
112
|
+
FORM_SELECT_FIELD_SCHEMA,
|
|
113
|
+
FORM_RADIO_FIELD_SCHEMA,
|
|
114
|
+
FORM_MULTI_SELECT_FIELD_SCHEMA,
|
|
115
|
+
FORM_CHECKBOX_FIELD_SCHEMA,
|
|
116
|
+
FORM_FILE_FIELD_SCHEMA,
|
|
117
|
+
]),
|
|
118
|
+
z.transform((field) => ({
|
|
119
|
+
...field,
|
|
120
|
+
label:
|
|
121
|
+
field.label ??
|
|
122
|
+
`${sentenceCase(field.name)}${field.type === "checkbox" ? "?" : ""}`,
|
|
123
|
+
})),
|
|
124
|
+
)
|
|
125
|
+
export const FORM_FIELDS_SCHEMA = z.array(FORM_FIELD_SCHEMA).check(
|
|
126
|
+
z.refine(
|
|
127
|
+
(fields) =>
|
|
128
|
+
new Set(fields.map((field) => field.name)).size === fields.length,
|
|
129
|
+
"Form field names must be unique.",
|
|
130
|
+
),
|
|
131
|
+
z.refine(
|
|
132
|
+
(fields) => fields.every(isFormFieldDefaultValid),
|
|
133
|
+
"Form field defaults must satisfy their field constraints.",
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
export type FormFieldValue = File | boolean | number | string | string[] | null
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns the configured fallback for a field without an assigned value.
|
|
141
|
+
*
|
|
142
|
+
* @param field - Field whose fallback should be resolved.
|
|
143
|
+
*/
|
|
144
|
+
export function getFormFieldDefaultValue(field: FormField) {
|
|
145
|
+
if (field.type === "file") return field.required === false ? null : undefined
|
|
146
|
+
if (field.defaultValue !== undefined) return field.defaultValue
|
|
147
|
+
if (field.type === "checkbox") return false
|
|
148
|
+
if (field.type === "multi-select") return []
|
|
149
|
+
if (field.type === "number")
|
|
150
|
+
return field.required === false ? null : undefined
|
|
151
|
+
return field.required === false ? "" : undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks one decoded value against a dashboard field declaration.
|
|
156
|
+
*
|
|
157
|
+
* @param field - Field declaration whose constraints apply.
|
|
158
|
+
* @param value - Decoded value to validate.
|
|
159
|
+
*/
|
|
160
|
+
export function isFormFieldValueValid(
|
|
161
|
+
field: FormField,
|
|
162
|
+
value: unknown,
|
|
163
|
+
): value is FormFieldValue {
|
|
164
|
+
return getFormFieldValueSchema(field).safeParse(value).success
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds the shared validator for decoded field values.
|
|
169
|
+
*
|
|
170
|
+
* @param field - Field declaration whose constraints apply.
|
|
171
|
+
*/
|
|
172
|
+
export function getFormFieldValueSchema(field: FormField) {
|
|
173
|
+
return z.custom<FormFieldValue>(
|
|
174
|
+
(value) => getFormFieldError(field, value) === undefined,
|
|
175
|
+
{ error: (issue) => getFormFieldError(field, issue.input) },
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Normalizes controlled or prompted values before validation.
|
|
181
|
+
*
|
|
182
|
+
* @param field - Field declaration.
|
|
183
|
+
* @param value - Raw control value.
|
|
184
|
+
*/
|
|
185
|
+
export function normalizeFormFieldValue(field: FormField, value: unknown) {
|
|
186
|
+
if (field.type === "file") {
|
|
187
|
+
// Browsers submit an unnamed empty File when no file was selected.
|
|
188
|
+
return value instanceof File && !value.name && value.size === 0
|
|
189
|
+
? null
|
|
190
|
+
: value
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === "string") {
|
|
193
|
+
const text = value.trim()
|
|
194
|
+
return field.type === "number" ? (text ? Number(text) : null) : text
|
|
195
|
+
}
|
|
196
|
+
if (field.type === "multi-select" && Array.isArray(value)) {
|
|
197
|
+
return value
|
|
198
|
+
.map((entry: unknown) =>
|
|
199
|
+
typeof entry === "string" ? entry.trim() : entry,
|
|
200
|
+
)
|
|
201
|
+
.filter((entry) => entry !== "")
|
|
202
|
+
}
|
|
203
|
+
return value
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Reads and validates one submitted field, preserving uploaded File values.
|
|
208
|
+
*
|
|
209
|
+
* @param field - Field declaration.
|
|
210
|
+
* @param data - Submitted form data.
|
|
211
|
+
* @param name - Form name, including any UI namespace.
|
|
212
|
+
*/
|
|
213
|
+
export function readFormFieldValue(
|
|
214
|
+
field: FormField,
|
|
215
|
+
data: FormData,
|
|
216
|
+
name = field.name,
|
|
217
|
+
) {
|
|
218
|
+
// Preserve the transport value before applying field-specific normalization.
|
|
219
|
+
const raw =
|
|
220
|
+
field.type === "checkbox"
|
|
221
|
+
? data.has(name)
|
|
222
|
+
: field.type === "multi-select"
|
|
223
|
+
? data.getAll(name)
|
|
224
|
+
: (data.get(name) ?? (field.type === "file" ? null : ""))
|
|
225
|
+
return getFormFieldValueSchema(field).safeParse(
|
|
226
|
+
normalizeFormFieldValue(field, raw),
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Validates configured form fields and ignores unconfigured entries.
|
|
232
|
+
*
|
|
233
|
+
* @param fields - Ordered field declarations.
|
|
234
|
+
* @param data - Submitted form data.
|
|
235
|
+
*/
|
|
236
|
+
export function parseFormFields(fields: readonly FormField[], data: FormData) {
|
|
237
|
+
const results = fields.map((field) => ({
|
|
238
|
+
field,
|
|
239
|
+
result: readFormFieldValue(field, data),
|
|
240
|
+
}))
|
|
241
|
+
const issues = results.flatMap(({ field, result }) =>
|
|
242
|
+
result.success
|
|
243
|
+
? []
|
|
244
|
+
: [
|
|
245
|
+
{
|
|
246
|
+
field: field.name,
|
|
247
|
+
message: result.error.issues[0]!.message,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
)
|
|
251
|
+
if (issues.length > 0) return { issues, success: false as const }
|
|
252
|
+
return {
|
|
253
|
+
data: Object.fromEntries(
|
|
254
|
+
results.flatMap(({ field, result }) =>
|
|
255
|
+
result.success ? [[field.name, result.data]] : [],
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
success: true as const,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Reports the first violated field constraint for every input surface.
|
|
264
|
+
*
|
|
265
|
+
* @param field - Field declaration.
|
|
266
|
+
* @param value - Decoded value.
|
|
267
|
+
*/
|
|
268
|
+
function getFormFieldError(
|
|
269
|
+
field: FormField,
|
|
270
|
+
value: unknown,
|
|
271
|
+
): string | undefined {
|
|
272
|
+
const label = field.label ?? sentenceCase(field.name)
|
|
273
|
+
const required = `${label} is required.`
|
|
274
|
+
const invalid = `${label} is invalid.`
|
|
275
|
+
if (field.type === "file") {
|
|
276
|
+
if (value === null) return field.required === false ? undefined : required
|
|
277
|
+
return value instanceof File ? undefined : `${label} must be a file.`
|
|
278
|
+
}
|
|
279
|
+
if (field.type === "checkbox") {
|
|
280
|
+
if (typeof value !== "boolean") return invalid
|
|
281
|
+
return field.required === true && !value ? required : undefined
|
|
282
|
+
}
|
|
283
|
+
if (field.type === "multi-select") {
|
|
284
|
+
if (!Array.isArray(value)) return invalid
|
|
285
|
+
if (field.required !== false && value.length === 0) return required
|
|
286
|
+
return value.every(
|
|
287
|
+
(entry) =>
|
|
288
|
+
typeof entry === "string" &&
|
|
289
|
+
field.options.some((option) => option.value === entry),
|
|
290
|
+
)
|
|
291
|
+
? undefined
|
|
292
|
+
: invalid
|
|
293
|
+
}
|
|
294
|
+
if (field.type === "number") {
|
|
295
|
+
if (value === null) return field.required === false ? undefined : required
|
|
296
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
297
|
+
return `${label} must be a number.`
|
|
298
|
+
if (field.min !== undefined && value < field.min)
|
|
299
|
+
return `${label} must be at least ${field.min}.`
|
|
300
|
+
if (field.max !== undefined && value > field.max)
|
|
301
|
+
return `${label} must be at most ${field.max}.`
|
|
302
|
+
const step = field.step ?? 1
|
|
303
|
+
return isFormStepMismatch(value, field.min ?? field.defaultValue ?? 0, step)
|
|
304
|
+
? `${label} must use increments of ${step}.`
|
|
305
|
+
: undefined
|
|
306
|
+
}
|
|
307
|
+
if (typeof value !== "string") return invalid
|
|
308
|
+
const text =
|
|
309
|
+
field.type === "radio" ||
|
|
310
|
+
field.type === "select" ||
|
|
311
|
+
field.type === "date" ||
|
|
312
|
+
field.type === "datetime"
|
|
313
|
+
? value
|
|
314
|
+
: value.trim()
|
|
315
|
+
if (text === "") return field.required === false ? undefined : required
|
|
316
|
+
if (field.type === "radio" || field.type === "select") {
|
|
317
|
+
return field.options.some((option) => option.value === text)
|
|
318
|
+
? undefined
|
|
319
|
+
: invalid
|
|
320
|
+
}
|
|
321
|
+
if (field.type === "date" || field.type === "datetime") {
|
|
322
|
+
if (
|
|
323
|
+
!(field.type === "date" ? DATE_SCHEMA : DATETIME_SCHEMA).safeParse(text)
|
|
324
|
+
.success
|
|
325
|
+
)
|
|
326
|
+
return invalid
|
|
327
|
+
const date = parseFormDateValue(field.type, text)
|
|
328
|
+
if (!Number.isFinite(date)) return invalid
|
|
329
|
+
if (
|
|
330
|
+
field.min !== undefined &&
|
|
331
|
+
date < parseFormDateValue(field.type, field.min)
|
|
332
|
+
)
|
|
333
|
+
return `${label} must be on or after ${field.min}.`
|
|
334
|
+
if (
|
|
335
|
+
field.max !== undefined &&
|
|
336
|
+
date > parseFormDateValue(field.type, field.max)
|
|
337
|
+
)
|
|
338
|
+
return `${label} must be on or before ${field.max}.`
|
|
339
|
+
// HTML temporal steps are measured from this configured origin.
|
|
340
|
+
const base = parseFormDateValue(
|
|
341
|
+
field.type,
|
|
342
|
+
field.min ??
|
|
343
|
+
field.defaultValue ??
|
|
344
|
+
(field.type === "date" ? "1970-01-01" : "1970-01-01T00:00"),
|
|
345
|
+
)
|
|
346
|
+
// Compare timestamps using millisecond intervals.
|
|
347
|
+
const step =
|
|
348
|
+
(field.step ?? (field.type === "date" ? 1 : 60)) *
|
|
349
|
+
(field.type === "date" ? 86_400_000 : 1_000)
|
|
350
|
+
return isFormStepMismatch(date, base, step)
|
|
351
|
+
? `${label} does not match the configured step.`
|
|
352
|
+
: undefined
|
|
353
|
+
}
|
|
354
|
+
if (field.minLength !== undefined && text.length < field.minLength)
|
|
355
|
+
return `${label} must contain at least ${field.minLength} characters.`
|
|
356
|
+
if (field.maxLength !== undefined && text.length > field.maxLength)
|
|
357
|
+
return `${label} must contain at most ${field.maxLength} characters.`
|
|
358
|
+
if (field.type === "email" && !EMAIL_SCHEMA.safeParse(text).success)
|
|
359
|
+
return `${label} must be an email address.`
|
|
360
|
+
if (field.type === "url" && !URL_SCHEMA.safeParse(text).success)
|
|
361
|
+
return `${label} must be a URL.`
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
interface FormFieldBase {
|
|
365
|
+
/** Supporting text shown below the field. */
|
|
366
|
+
description?: string
|
|
367
|
+
|
|
368
|
+
/** Visible field label. Inferred from `name` when omitted. */
|
|
369
|
+
label?: string
|
|
370
|
+
|
|
371
|
+
/** Property name used in submitted trigger data. */
|
|
372
|
+
name: string
|
|
373
|
+
|
|
374
|
+
/** Whether the form must contain a value. Defaults to true. */
|
|
375
|
+
required?: boolean
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export type FormField =
|
|
379
|
+
| (FormFieldBase & { type: "file" })
|
|
380
|
+
| (FormFieldBase & {
|
|
381
|
+
defaultValue?: string
|
|
382
|
+
maxLength?: number
|
|
383
|
+
minLength?: number
|
|
384
|
+
placeholder?: string
|
|
385
|
+
type: "email" | "phone" | "text" | "textarea" | "url"
|
|
386
|
+
})
|
|
387
|
+
| (FormFieldBase & {
|
|
388
|
+
defaultValue?: number
|
|
389
|
+
max?: number
|
|
390
|
+
min?: number
|
|
391
|
+
placeholder?: string
|
|
392
|
+
|
|
393
|
+
/** Increment used by the number input. Defaults to 1. */
|
|
394
|
+
step?: number
|
|
395
|
+
type: "number"
|
|
396
|
+
})
|
|
397
|
+
| (FormFieldBase & {
|
|
398
|
+
/** Initial local date in `YYYY-MM-DD` format. */
|
|
399
|
+
defaultValue?: string
|
|
400
|
+
max?: string
|
|
401
|
+
min?: string
|
|
402
|
+
|
|
403
|
+
/** Allowed increment in days. Defaults to 1. */
|
|
404
|
+
step?: number
|
|
405
|
+
type: "date"
|
|
406
|
+
})
|
|
407
|
+
| (FormFieldBase & {
|
|
408
|
+
/** Initial local date and time in `YYYY-MM-DDTHH:mm` format. */
|
|
409
|
+
defaultValue?: string
|
|
410
|
+
max?: string
|
|
411
|
+
min?: string
|
|
412
|
+
|
|
413
|
+
/** Allowed increment in seconds. Defaults to 60. */
|
|
414
|
+
step?: number
|
|
415
|
+
type: "datetime"
|
|
416
|
+
})
|
|
417
|
+
| (FormFieldBase & {
|
|
418
|
+
defaultValue?: string
|
|
419
|
+
options: readonly { label: string; value: string }[]
|
|
420
|
+
placeholder?: string
|
|
421
|
+
type: "select"
|
|
422
|
+
})
|
|
423
|
+
| (FormFieldBase & {
|
|
424
|
+
defaultValue?: string
|
|
425
|
+
options: readonly { label: string; value: string }[]
|
|
426
|
+
type: "radio"
|
|
427
|
+
})
|
|
428
|
+
| (FormFieldBase & {
|
|
429
|
+
defaultValue?: readonly string[]
|
|
430
|
+
options: readonly { label: string; value: string }[]
|
|
431
|
+
placeholder?: string
|
|
432
|
+
type: "multi-select"
|
|
433
|
+
})
|
|
434
|
+
| (FormFieldBase & {
|
|
435
|
+
/** Initial checked state. Defaults to false. */
|
|
436
|
+
defaultValue?: boolean
|
|
437
|
+
|
|
438
|
+
/** Whether the checkbox must be checked. Defaults to false. */
|
|
439
|
+
required?: boolean
|
|
440
|
+
type: "checkbox"
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Checks a configured default against the same constraints as submitted data.
|
|
445
|
+
*
|
|
446
|
+
* @param field - Field definition to validate.
|
|
447
|
+
*/
|
|
448
|
+
function isFormFieldDefaultValid(field: FormField) {
|
|
449
|
+
if (
|
|
450
|
+
field.type === "file" ||
|
|
451
|
+
field.type === "checkbox" ||
|
|
452
|
+
field.defaultValue === undefined
|
|
453
|
+
)
|
|
454
|
+
return true
|
|
455
|
+
return isFormFieldValueValid(field, field.defaultValue)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Checks whether a value falls outside an allowed step interval.
|
|
460
|
+
*
|
|
461
|
+
* @param value - Configured default value.
|
|
462
|
+
* @param base - Value from which step intervals begin.
|
|
463
|
+
* @param step - Allowed interval size.
|
|
464
|
+
*/
|
|
465
|
+
function isFormStepMismatch(value: number, base: number, step: number) {
|
|
466
|
+
const intervals = (value - base) / step
|
|
467
|
+
return Math.abs(intervals - Math.round(intervals)) > 1e-9
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Converts a validated local date or date-time string to a UTC number.
|
|
472
|
+
*
|
|
473
|
+
* @param type - Temporal field type.
|
|
474
|
+
* @param value - Local ISO value to convert.
|
|
475
|
+
*/
|
|
476
|
+
function parseFormDateValue(type: "date" | "datetime", value: string) {
|
|
477
|
+
return Date.parse(type === "date" ? `${value}T00:00:00Z` : `${value}Z`)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Resolves one field declaration to its author-facing value type. */
|
|
481
|
+
type FormFieldValueFor<TField extends FormField> = TField extends {
|
|
482
|
+
type: "file"
|
|
483
|
+
}
|
|
484
|
+
? TField extends { required: false }
|
|
485
|
+
? File | null
|
|
486
|
+
: File
|
|
487
|
+
: TField extends { type: "checkbox" }
|
|
488
|
+
? boolean
|
|
489
|
+
: TField extends {
|
|
490
|
+
options: readonly (infer TOption)[]
|
|
491
|
+
type: "multi-select"
|
|
492
|
+
}
|
|
493
|
+
? [TOption] extends [{ value: infer TValue extends string }]
|
|
494
|
+
? TValue[]
|
|
495
|
+
: string[]
|
|
496
|
+
: TField extends { required: false; type: "number" }
|
|
497
|
+
? number | null
|
|
498
|
+
: TField extends { type: "number" }
|
|
499
|
+
? number
|
|
500
|
+
: TField extends {
|
|
501
|
+
options: readonly (infer TOption)[]
|
|
502
|
+
type: "radio" | "select"
|
|
503
|
+
}
|
|
504
|
+
? TOption extends { value: infer TValue extends string }
|
|
505
|
+
? TField extends { required: false }
|
|
506
|
+
? TValue | ""
|
|
507
|
+
: TValue
|
|
508
|
+
: string
|
|
509
|
+
: string
|
|
510
|
+
|
|
511
|
+
export type FormFieldValues<TFields extends readonly FormField[]> = {
|
|
512
|
+
readonly [TField in TFields[number] as TField["name"]]: FormFieldValueFor<TField>
|
|
513
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -92,9 +92,6 @@ export {
|
|
|
92
92
|
getMailhookAddress,
|
|
93
93
|
getMailhookAddressKey,
|
|
94
94
|
DASHBOARD_RUN_CONFIG_SCHEMA,
|
|
95
|
-
DASHBOARD_RUN_FIELDS_SCHEMA,
|
|
96
|
-
getDashboardRunFieldDefaultValue,
|
|
97
|
-
isDashboardRunFieldValueValid,
|
|
98
95
|
getTriggerDefinition,
|
|
99
96
|
getTriggerName,
|
|
100
97
|
getTriggerPresentation,
|
|
@@ -104,11 +101,22 @@ export {
|
|
|
104
101
|
triggerDefinitions,
|
|
105
102
|
type DashboardRunConfig,
|
|
106
103
|
type DashboardRunEndpointOptions,
|
|
107
|
-
type DashboardRunField,
|
|
108
|
-
type DashboardRunFieldValue,
|
|
109
104
|
type HttpTriggerEndpointOptions,
|
|
110
105
|
type HttpTriggerScope,
|
|
111
106
|
type MailhookAddressOptions,
|
|
112
107
|
type MailhookScope,
|
|
113
108
|
type TriggerType,
|
|
114
109
|
} from "./triggers"
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
FORM_FIELDS_SCHEMA,
|
|
113
|
+
getFormFieldDefaultValue,
|
|
114
|
+
getFormFieldValueSchema,
|
|
115
|
+
isFormFieldValueValid,
|
|
116
|
+
normalizeFormFieldValue,
|
|
117
|
+
readFormFieldValue,
|
|
118
|
+
parseFormFields,
|
|
119
|
+
type FormField,
|
|
120
|
+
type FormFieldValue,
|
|
121
|
+
type FormFieldValues,
|
|
122
|
+
} from "./form-fields"
|