@orkestrel/form 0.0.1
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/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/src/core/index.cjs +1758 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1160 -0
- package/dist/src/core/index.d.ts +1160 -0
- package/dist/src/core/index.js +1704 -0
- package/dist/src/core/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
2
|
+
import { EmitterHooks } from '@orkestrel/emitter';
|
|
3
|
+
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
|
+
import { JSONRecord } from '@orkestrel/contract';
|
|
5
|
+
import { Result } from '@orkestrel/contract';
|
|
6
|
+
|
|
7
|
+
/** One or more ASCII letters or digits. */
|
|
8
|
+
export declare const ALPHANUMERIC_PATTERN: Readonly<RegExp>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check whether a named rule applies to one field control.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* The runtime control-membership check keeps this boundary total for JavaScript callers that
|
|
15
|
+
* bypass the declared {@link FieldControl} contract.
|
|
16
|
+
*
|
|
17
|
+
* @param control - The field control to inspect.
|
|
18
|
+
* @param rule - The named rule to inspect.
|
|
19
|
+
* @returns Whether the control evaluates that rule.
|
|
20
|
+
*/
|
|
21
|
+
export declare function appliesRule(control: FieldControl, rule: FieldRuleName): boolean;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Audit a structurally valid schema for domain invariants.
|
|
25
|
+
*
|
|
26
|
+
* @param schema - The form schema to audit.
|
|
27
|
+
* @returns Human-readable invariant violations, or an empty list when the schema is sound.
|
|
28
|
+
*/
|
|
29
|
+
export declare function auditSchema(schema: FormSchema): readonly string[];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Any number of choices out of a list, holding the checked values.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* A field offering one box that means yes or no is a {@link ConfirmField}, not a one-choice
|
|
36
|
+
* checkbox.
|
|
37
|
+
*/
|
|
38
|
+
export declare interface CheckboxField extends FieldBase {
|
|
39
|
+
readonly control: 'checkbox';
|
|
40
|
+
readonly choices: readonly FieldChoice[];
|
|
41
|
+
readonly default?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The maximum number of choices one `select` or `checkbox` field may offer. */
|
|
45
|
+
export declare const CHOICE_LIMIT = 1024;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clone a field's choices into an owned frozen snapshot.
|
|
49
|
+
*
|
|
50
|
+
* @param choices - The choices to own.
|
|
51
|
+
* @returns A frozen list of frozen choice records.
|
|
52
|
+
*/
|
|
53
|
+
export declare function cloneChoices(choices: readonly FieldChoice[]): readonly FieldChoice[];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Clone one form field into an owned frozen snapshot.
|
|
57
|
+
*
|
|
58
|
+
* @param field - The field to own.
|
|
59
|
+
* @returns A frozen field with every nested collection owned.
|
|
60
|
+
* @throws A {@link FormError} coded `SCHEMA` when accessor-bearing metadata cannot be owned.
|
|
61
|
+
*/
|
|
62
|
+
export declare function cloneFormField(field: FormField): FormField;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clone a form schema into an owned frozen snapshot.
|
|
66
|
+
*
|
|
67
|
+
* @param schema - The schema to own.
|
|
68
|
+
* @returns A frozen schema with every nested record and list owned.
|
|
69
|
+
*/
|
|
70
|
+
export declare function cloneFormSchema(schema: FormSchema): FormSchema;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clone one form value into an owned frozen snapshot.
|
|
74
|
+
*
|
|
75
|
+
* @param value - The field value to own.
|
|
76
|
+
* @returns The scalar unchanged, or a frozen copy of the list.
|
|
77
|
+
*/
|
|
78
|
+
export declare function cloneValue(value: readonly string[]): readonly string[];
|
|
79
|
+
|
|
80
|
+
export declare function cloneValue(value: FieldValue): FieldValue;
|
|
81
|
+
|
|
82
|
+
/** A six-digit hexadecimal color string. */
|
|
83
|
+
export declare const COLOR_PATTERN: Readonly<RegExp>;
|
|
84
|
+
|
|
85
|
+
/** A color, held as the control's own string. */
|
|
86
|
+
export declare interface ColorField extends FieldBase {
|
|
87
|
+
readonly control: 'color';
|
|
88
|
+
readonly default?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Compute the values explicitly seeded by a schema.
|
|
93
|
+
*
|
|
94
|
+
* @param schema - The schema whose defaults to collect.
|
|
95
|
+
* @returns A value record containing only fields with defaults.
|
|
96
|
+
*/
|
|
97
|
+
export declare function computeDefaults(schema: FormSchema): FormValues;
|
|
98
|
+
|
|
99
|
+
/** A single on/off box, holding a boolean. */
|
|
100
|
+
export declare interface ConfirmField extends FieldBase {
|
|
101
|
+
readonly control: 'confirm';
|
|
102
|
+
readonly default?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Open a form against a schema.
|
|
107
|
+
*
|
|
108
|
+
* @param schema - The form to ask. It is copied, and the copy is what the form asks.
|
|
109
|
+
* @param options - The form's settings.
|
|
110
|
+
* @returns A form open for answers.
|
|
111
|
+
* @remarks
|
|
112
|
+
* Prefer this at a call site that only needs {@link FormInterface}. `new Form(...)` is the same
|
|
113
|
+
* construction and is what a class holding a form as its own field reaches for.
|
|
114
|
+
* @throws A {@link FormError} coded `SCHEMA` when the schema is malformed, `FIELD` when
|
|
115
|
+
* `options.values` names a field the schema does not declare, and `CONTROL` when a seeded value
|
|
116
|
+
* is one its field's control cannot hold.
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const form = createForm({
|
|
120
|
+
* label: 'Sign up',
|
|
121
|
+
* fields: [
|
|
122
|
+
* { control: 'text', name: 'email', label: 'Email', rule: { required: true, email: true } },
|
|
123
|
+
* { control: 'confirm', name: 'terms', label: 'I accept the terms', rule: { required: true } },
|
|
124
|
+
* ],
|
|
125
|
+
* })
|
|
126
|
+
*
|
|
127
|
+
* form.fill({ email: 'ada@example.com', terms: true })
|
|
128
|
+
* form.submit() // { success: true, value: { email: 'ada@example.com', terms: true } }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export declare function createForm(schema: FormSchema, options?: FormOptions): FormInterface;
|
|
132
|
+
|
|
133
|
+
/** An ISO calendar date string in `YYYY-MM-DD` form. */
|
|
134
|
+
export declare const DATE_PATTERN: Readonly<RegExp>;
|
|
135
|
+
|
|
136
|
+
/** A calendar date, held as the control's own string. */
|
|
137
|
+
export declare interface DateField extends FieldBase {
|
|
138
|
+
readonly control: 'date';
|
|
139
|
+
readonly default?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** An ISO local date and time string with optional seconds. */
|
|
143
|
+
export declare const DATETIME_PATTERN: Readonly<RegExp>;
|
|
144
|
+
|
|
145
|
+
/** A date and a time of day together, with no zone, held as the control's own string. */
|
|
146
|
+
export declare interface DatetimeField extends FieldBase {
|
|
147
|
+
readonly control: 'datetime';
|
|
148
|
+
readonly default?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Text over many lines. */
|
|
152
|
+
export declare interface EditorField extends FieldBase {
|
|
153
|
+
readonly control: 'editor';
|
|
154
|
+
readonly default?: string;
|
|
155
|
+
readonly placeholder?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** A practical whole-address email shape. */
|
|
159
|
+
export declare const EMAIL_PATTERN: Readonly<RegExp>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Evaluate one field rule against its current value.
|
|
163
|
+
*
|
|
164
|
+
* @param field - The field and rule to evaluate.
|
|
165
|
+
* @param value - The current value, or absence.
|
|
166
|
+
* @param values - Every value available to a custom rule.
|
|
167
|
+
* @param messages - Optional rule-specific message replacements.
|
|
168
|
+
* @returns Every failure in rule order.
|
|
169
|
+
*/
|
|
170
|
+
export declare function evaluateField(field: FormField, value: FieldValue | undefined, values: FormValues, messages?: Readonly<Partial<Record<FieldRuleName, string>>>): readonly FieldError[];
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Evaluate every active field in schema order.
|
|
174
|
+
*
|
|
175
|
+
* @param schema - The form schema to evaluate.
|
|
176
|
+
* @param values - The values keyed by field name.
|
|
177
|
+
* @param options - Optional message replacements and the effective disabled field set.
|
|
178
|
+
* @returns Every field failure in schema and rule order.
|
|
179
|
+
*/
|
|
180
|
+
export declare function evaluateForm(schema: FormSchema, values: FormValues, options?: EvaluationOptions): readonly FieldError[];
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* How to check a schema against a set of answers.
|
|
184
|
+
*
|
|
185
|
+
* @param options - The evaluation's settings.
|
|
186
|
+
* @remarks
|
|
187
|
+
* `messages` replaces the default message of a rule, keyed by {@link FieldRuleName}.
|
|
188
|
+
*
|
|
189
|
+
* `disabled` names the fields to leave out of the check. It replaces the schema's own
|
|
190
|
+
* {@link FieldBase.disabled} declarations rather than adding to them, because a live form's
|
|
191
|
+
* answer to which fields are in play is {@link FormInterface.disabled}, and a form always
|
|
192
|
+
* supplies that set.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const options: EvaluationOptions = {
|
|
197
|
+
* messages: { required: 'This one is needed' },
|
|
198
|
+
* disabled: new Set(['nickname']),
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export declare interface EvaluationOptions {
|
|
203
|
+
readonly messages?: Readonly<Partial<Record<FieldRuleName, string>>>;
|
|
204
|
+
readonly disabled?: ReadonlySet<string>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Extract the names whose answers differ between two form value records.
|
|
209
|
+
*
|
|
210
|
+
* @remarks
|
|
211
|
+
* Presence is compared in both directions before present values are compared through
|
|
212
|
+
* {@link matchesValue}. The returned set is a new snapshot, exposed as readonly because later
|
|
213
|
+
* changes to either input never alter its membership.
|
|
214
|
+
*
|
|
215
|
+
* @param current - The values held now.
|
|
216
|
+
* @param opened - The values held when the form opened.
|
|
217
|
+
* @returns A readonly snapshot of changed field names.
|
|
218
|
+
*/
|
|
219
|
+
export declare function extractChanges(current: FormValues, opened: FormValues): ReadonlySet<string>;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Select referenced groups in first-reference field order.
|
|
223
|
+
*
|
|
224
|
+
* @param schema - The schema whose group references to resolve.
|
|
225
|
+
* @returns The referenced schema groups without duplicates.
|
|
226
|
+
*/
|
|
227
|
+
export declare function extractGroups(schema: FormSchema): readonly FormGroup[];
|
|
228
|
+
|
|
229
|
+
/** Every field control, in the order declared by the public contract. */
|
|
230
|
+
export declare const FIELD_CONTROLS: readonly FieldControl[];
|
|
231
|
+
|
|
232
|
+
/** The maximum number of fields one schema may declare. */
|
|
233
|
+
export declare const FIELD_LIMIT = 512;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* What every field carries, whatever its control.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
* `name` keys the field in {@link FormValues} and `group` names a {@link FormGroup}.
|
|
240
|
+
*
|
|
241
|
+
* The three visibility switches differ in what they remove. `hidden` keeps the field out of
|
|
242
|
+
* the rendered form, `locked` renders it unwritable, and both are still validated and still
|
|
243
|
+
* submitted. `disabled` takes the field out of the form entirely: it is neither validated nor
|
|
244
|
+
* submitted. It is the field's declared, opening state; {@link FormInterface.disabled} is the
|
|
245
|
+
* current fact, because a live form can move a field either way.
|
|
246
|
+
*
|
|
247
|
+
* `meta` is a bounded JSON carrier for whatever the schema declines to model. Evaluation never
|
|
248
|
+
* reads it, no rule sees it, and it round-trips verbatim through serialization. This package
|
|
249
|
+
* defines no key in it, so every key belongs to the host.
|
|
250
|
+
*/
|
|
251
|
+
export declare interface FieldBase {
|
|
252
|
+
readonly name: string;
|
|
253
|
+
readonly label?: string;
|
|
254
|
+
readonly help?: string;
|
|
255
|
+
readonly group?: string;
|
|
256
|
+
readonly hidden?: boolean;
|
|
257
|
+
readonly disabled?: boolean;
|
|
258
|
+
readonly locked?: boolean;
|
|
259
|
+
readonly rule?: FieldRule;
|
|
260
|
+
readonly meta?: JSONRecord;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* One option a `select` or `checkbox` field offers.
|
|
265
|
+
*
|
|
266
|
+
* @remarks
|
|
267
|
+
* `value` is what the form stores and `label` is what the person reads. `help` explains the
|
|
268
|
+
* option, and `disabled` shows it while refusing its value at every input door, including
|
|
269
|
+
* seeded values.
|
|
270
|
+
*/
|
|
271
|
+
export declare interface FieldChoice {
|
|
272
|
+
readonly value: string;
|
|
273
|
+
readonly label: string;
|
|
274
|
+
readonly help?: string;
|
|
275
|
+
readonly disabled?: boolean;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The control a field presents to the person answering it.
|
|
280
|
+
*
|
|
281
|
+
* @remarks
|
|
282
|
+
* The control is the discriminant of every {@link FormField} variant, so choosing it fixes
|
|
283
|
+
* which options that field accepts and which {@link FieldValue} it holds.
|
|
284
|
+
*
|
|
285
|
+
* Three members need saying out loud. `confirm` is a single on/off box holding a boolean, so
|
|
286
|
+
* a lone browser checkbox is a `confirm`. `checkbox` is the multi-choice group holding the
|
|
287
|
+
* checked values as a list, never a single box. `datetime` is the browser's `datetime-local`:
|
|
288
|
+
* a wall-clock date and time carrying no zone.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* const control: FieldControl = 'select'
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
export declare type FieldControl = 'text' | 'editor' | 'password' | 'number' | 'date' | 'time' | 'datetime' | 'color' | 'confirm' | 'select' | 'checkbox' | 'file';
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* One failed check against one field.
|
|
299
|
+
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* `rule` names the constraint that failed. It is absent when the message came from a
|
|
302
|
+
* {@link FieldRule.custom} validator or from {@link FormInterface.invalidate}, because
|
|
303
|
+
* neither failure belongs to a named rule.
|
|
304
|
+
*/
|
|
305
|
+
export declare interface FieldError {
|
|
306
|
+
readonly field: string;
|
|
307
|
+
readonly message: string;
|
|
308
|
+
readonly rule?: FieldRuleName;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The constraints one field's value must satisfy.
|
|
313
|
+
*
|
|
314
|
+
* @remarks
|
|
315
|
+
* `minimum` and `maximum` measure whatever the control makes countable: characters for
|
|
316
|
+
* `text`, `editor`, and `password`; magnitude for `number`; chronology for `date`, `time`,
|
|
317
|
+
* and `datetime`, whose operand is a string written in that control's own format; and the
|
|
318
|
+
* number of selections for `checkbox` and `file`.
|
|
319
|
+
*
|
|
320
|
+
* `step` is the interval a numeric value must land on. `pattern` is regular-expression
|
|
321
|
+
* source. `email`, `url`, `integer`, and `alphanumeric` each assert one shape over the whole
|
|
322
|
+
* value. `custom` runs last and is the only rule that sees the rest of the form.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* const rule: FieldRule = { required: true, minimum: 8, pattern: '\\d' }
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
export declare interface FieldRule {
|
|
330
|
+
readonly required?: boolean;
|
|
331
|
+
readonly minimum?: number | string;
|
|
332
|
+
readonly maximum?: number | string;
|
|
333
|
+
readonly step?: number;
|
|
334
|
+
readonly pattern?: string;
|
|
335
|
+
readonly email?: boolean;
|
|
336
|
+
readonly url?: boolean;
|
|
337
|
+
readonly integer?: boolean;
|
|
338
|
+
readonly alphanumeric?: boolean;
|
|
339
|
+
readonly custom?: FieldValidator;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Every rule that reports its failure by name.
|
|
344
|
+
*
|
|
345
|
+
* @remarks
|
|
346
|
+
* `custom` is excluded because it supplies its own message, so nothing keyed by a rule name
|
|
347
|
+
* would ever be read for it. {@link FormOptions.messages} is keyed by this name.
|
|
348
|
+
*/
|
|
349
|
+
export declare type FieldRuleName = Exclude<keyof FieldRule, 'custom'>;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check one value against the whole form.
|
|
353
|
+
*
|
|
354
|
+
* @remarks
|
|
355
|
+
* It runs after every named rule, and it runs on an absent value as well as a present one. That
|
|
356
|
+
* is what makes a rule such as "required once the sibling says yes" expressible, and it means an
|
|
357
|
+
* unanswered field can carry both a `required` message and this validator's own.
|
|
358
|
+
*
|
|
359
|
+
* @param value - The value the field currently holds, or `undefined` when nobody has answered it.
|
|
360
|
+
* @param values - Every answer the form holds, so a rule can read its siblings.
|
|
361
|
+
* @returns `true` when the value passes, or the message explaining why it failed.
|
|
362
|
+
* @throws The validator's own thrown value escapes the mutation call unchanged. When a form
|
|
363
|
+
* mutation has already changed state, the throw leaves those changes beside the error list
|
|
364
|
+
* from before that mutation. An invalidation can therefore be recorded without appearing in
|
|
365
|
+
* `errors`, and a clear can reset state without emitting `clear`. Form-owned refusals use
|
|
366
|
+
* {@link FormError} instead.
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* const matches: FieldValidator = (value, values) =>
|
|
370
|
+
* value === values.password ? true : 'Both passwords must match'
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export declare type FieldValidator = (value: FieldValue | undefined, values: FormValues) => true | string;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Every value a field can hold.
|
|
377
|
+
*
|
|
378
|
+
* @remarks
|
|
379
|
+
* The variant follows the control: text-like controls hold a `string`, `number` holds a
|
|
380
|
+
* `number`, `confirm` holds a `boolean`, and `checkbox` holds its checked values as a
|
|
381
|
+
* `readonly string[]`.
|
|
382
|
+
*/
|
|
383
|
+
export declare type FieldValue = string | number | boolean | readonly string[];
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* One or more files.
|
|
387
|
+
*
|
|
388
|
+
* @remarks
|
|
389
|
+
* `accept` lists the media types and extensions the control offers, in the form the host
|
|
390
|
+
* expects.
|
|
391
|
+
*/
|
|
392
|
+
export declare interface FileField extends FieldBase {
|
|
393
|
+
readonly control: 'file';
|
|
394
|
+
readonly accept?: readonly string[];
|
|
395
|
+
readonly multiple?: boolean;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* A form: a schema, the answers given against it, and the errors they carry.
|
|
400
|
+
*
|
|
401
|
+
* @remarks
|
|
402
|
+
* The form owns its schema, so a later edit to the schema the caller passed changes nothing here.
|
|
403
|
+
*
|
|
404
|
+
* `errors` is recomputed at construction and after every mutation whose evaluation completes,
|
|
405
|
+
* and the `validate` event fires exactly when that list's content changes. A throwing custom
|
|
406
|
+
* validator escapes after any preceding state changes and leaves the prior error list in place.
|
|
407
|
+
* There is no separate check.
|
|
408
|
+
*
|
|
409
|
+
* `valid` and `dirty` are derived on read from the error list and answers respectively, never
|
|
410
|
+
* stored.
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* const form = new Form({
|
|
415
|
+
* fields: [{ control: 'text', name: 'email', rule: { required: true, email: true } }],
|
|
416
|
+
* })
|
|
417
|
+
*
|
|
418
|
+
* form.fill('email', 'ada@example.com')
|
|
419
|
+
* const result = form.submit()
|
|
420
|
+
* if (result.success) await form.answer
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
export declare class Form implements FormInterface {
|
|
424
|
+
#private;
|
|
425
|
+
/**
|
|
426
|
+
* Open a form against a schema.
|
|
427
|
+
*
|
|
428
|
+
* @param schema - The form to ask. It is copied, and the copy is what the form asks.
|
|
429
|
+
* @param options - The form's settings.
|
|
430
|
+
* @throws A {@link FormError} coded `SCHEMA` when the schema is malformed, `FIELD` when
|
|
431
|
+
* `options.values` names a field the schema does not declare, and `CONTROL` when a seeded
|
|
432
|
+
* value is one its field's control cannot hold.
|
|
433
|
+
*/
|
|
434
|
+
constructor(schema: FormSchema, options?: FormOptions);
|
|
435
|
+
/** The form's event emitter. */
|
|
436
|
+
get emitter(): EmitterInterface<FormEventMap>;
|
|
437
|
+
/** The schema this form asks, owned and frozen. */
|
|
438
|
+
get schema(): FormSchema;
|
|
439
|
+
/** The answers held right now. */
|
|
440
|
+
get values(): FormValues;
|
|
441
|
+
/** The answers the form opened with. */
|
|
442
|
+
get baseline(): FormValues;
|
|
443
|
+
/** Every error the last completed evaluation produced. */
|
|
444
|
+
get errors(): readonly FieldError[];
|
|
445
|
+
/** The names of the fields somebody has visited. */
|
|
446
|
+
get touched(): ReadonlySet<string>;
|
|
447
|
+
/** The names of the fields currently out of the form. */
|
|
448
|
+
get disabled(): ReadonlySet<string>;
|
|
449
|
+
/** Where the form sits in its life. */
|
|
450
|
+
get status(): FormStatus;
|
|
451
|
+
/** Whether the last completed evaluation found no error. */
|
|
452
|
+
get valid(): boolean;
|
|
453
|
+
/** Whether any answer has moved since the form opened. */
|
|
454
|
+
get dirty(): boolean;
|
|
455
|
+
/**
|
|
456
|
+
* The answers, once the form settles.
|
|
457
|
+
*
|
|
458
|
+
* @remarks
|
|
459
|
+
* It resolves with the submitted values on the first valid submit, and rejects with a
|
|
460
|
+
* {@link FormError} coded `ABANDONED` when teardown abandons the form before it settles.
|
|
461
|
+
*/
|
|
462
|
+
get answer(): Promise<FormValues>;
|
|
463
|
+
/**
|
|
464
|
+
* Find one field by name.
|
|
465
|
+
*
|
|
466
|
+
* @param name - The field's name.
|
|
467
|
+
* @returns The field, or `undefined` when the schema declares no such name.
|
|
468
|
+
*/
|
|
469
|
+
field(name: string): FormField | undefined;
|
|
470
|
+
/**
|
|
471
|
+
* Answer several fields at once.
|
|
472
|
+
*
|
|
473
|
+
* @param values - The answers to write, each keyed by its field name.
|
|
474
|
+
*/
|
|
475
|
+
fill(values: FormValues): void;
|
|
476
|
+
/**
|
|
477
|
+
* Answer one field.
|
|
478
|
+
*
|
|
479
|
+
* @param name - The field's name.
|
|
480
|
+
* @param value - The answer to write, or `undefined` to clear it.
|
|
481
|
+
*/
|
|
482
|
+
fill(name: string, value: FieldValue | undefined): void;
|
|
483
|
+
/**
|
|
484
|
+
* Record that somebody has visited a field.
|
|
485
|
+
*
|
|
486
|
+
* @param name - The field's name.
|
|
487
|
+
* @throws A {@link FormError} coded `SETTLED` or `ABANDONED` when the form has ended, and
|
|
488
|
+
* `FIELD` when the schema declares no such name.
|
|
489
|
+
*/
|
|
490
|
+
touch(name: string): void;
|
|
491
|
+
/**
|
|
492
|
+
* Fail a field from outside, for what the rules cannot see.
|
|
493
|
+
*
|
|
494
|
+
* @param name - The field's name.
|
|
495
|
+
* @param message - What to tell the person.
|
|
496
|
+
* @remarks
|
|
497
|
+
* One field holds one external failure: a second call replaces the first. The failure lasts
|
|
498
|
+
* until that field is filled again or the form is cleared.
|
|
499
|
+
* @throws A {@link FormError} coded `SETTLED` or `ABANDONED` when the form has ended, and
|
|
500
|
+
* `FIELD` when the schema declares no such name.
|
|
501
|
+
*/
|
|
502
|
+
invalidate(name: string, message: string): void;
|
|
503
|
+
/** Take every field out of the form. */
|
|
504
|
+
disable(): void;
|
|
505
|
+
/**
|
|
506
|
+
* Take one field out of the form.
|
|
507
|
+
*
|
|
508
|
+
* @param name - The field's name.
|
|
509
|
+
*/
|
|
510
|
+
disable(name: string): void;
|
|
511
|
+
/**
|
|
512
|
+
* Take several fields out of the form.
|
|
513
|
+
*
|
|
514
|
+
* @param names - The field names.
|
|
515
|
+
*/
|
|
516
|
+
disable(names: readonly string[]): void;
|
|
517
|
+
/** Put every field back into the form. */
|
|
518
|
+
enable(): void;
|
|
519
|
+
/**
|
|
520
|
+
* Put one field back into the form.
|
|
521
|
+
*
|
|
522
|
+
* @param name - The field's name.
|
|
523
|
+
*/
|
|
524
|
+
enable(name: string): void;
|
|
525
|
+
/**
|
|
526
|
+
* Put several fields back into the form.
|
|
527
|
+
*
|
|
528
|
+
* @param names - The field names.
|
|
529
|
+
*/
|
|
530
|
+
enable(names: readonly string[]): void;
|
|
531
|
+
/**
|
|
532
|
+
* Check every answer and settle the form when they all pass.
|
|
533
|
+
*
|
|
534
|
+
* @returns The values on success, or every error that stopped them.
|
|
535
|
+
* @remarks
|
|
536
|
+
* A failed submit marks every enabled field touched, so a renderer can show the errors the
|
|
537
|
+
* person has not reached yet. A disabled field is neither checked nor submitted. When a changed
|
|
538
|
+
* evaluation notifies listeners and a listener writes, submit evaluates once more after those
|
|
539
|
+
* listeners return and decides from that state. Listener work that settled the form wins: that
|
|
540
|
+
* settlement is what this call returns, with no further evaluation, resolution, or `submit`
|
|
541
|
+
* emission. An evaluation that already failed refuses with the list it checked, even when a
|
|
542
|
+
* listener repaired or disabled the field that failed. An evaluation that passed decides from the
|
|
543
|
+
* state the drain left, which is why one further evaluation bounds the drain rather than a
|
|
544
|
+
* fixpoint loop.
|
|
545
|
+
* @throws A {@link FormError} coded `SETTLED` or `ABANDONED` when the form has ended.
|
|
546
|
+
*/
|
|
547
|
+
submit(): FormResult;
|
|
548
|
+
/**
|
|
549
|
+
* Return every answer to the ones the form opened with: the schema's defaults, overlaid with
|
|
550
|
+
* any seeded `values`. Reset the runtime disabled state to the schema's declarations.
|
|
551
|
+
*
|
|
552
|
+
* @throws A {@link FormError} coded `SETTLED` or `ABANDONED` when the form has ended.
|
|
553
|
+
*/
|
|
554
|
+
clear(): void;
|
|
555
|
+
/**
|
|
556
|
+
* Tear the form down, abandoning it when it has not settled.
|
|
557
|
+
*
|
|
558
|
+
* @remarks
|
|
559
|
+
* Destroying twice does nothing the second time. A settled form keeps its `settled` status and
|
|
560
|
+
* announces nothing. A request from inside a listener defers teardown until the outermost
|
|
561
|
+
* mutation batch closes, so an in-flight settlement can win and leave the form `settled` rather
|
|
562
|
+
* than `abandoned`. Every getter keeps answering afterwards; every write is refused.
|
|
563
|
+
*/
|
|
564
|
+
destroy(): void;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Every form lifecycle status. */
|
|
568
|
+
export declare const FORM_STATUSES: readonly FormStatus[];
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Resolve and interpolate one rule message.
|
|
572
|
+
*
|
|
573
|
+
* @param rule - The rule whose message to resolve.
|
|
574
|
+
* @param limit - The optional operand substituted for `{limit}`.
|
|
575
|
+
* @param messages - Optional rule-specific message replacements.
|
|
576
|
+
* @returns The resolved failure text.
|
|
577
|
+
*/
|
|
578
|
+
export declare function formatMessage(rule: FieldRuleName, limit?: number | string, messages?: Readonly<Partial<Record<FieldRuleName, string>>>): string;
|
|
579
|
+
|
|
580
|
+
/** An error raised by the form domain. */
|
|
581
|
+
export declare class FormError extends Error {
|
|
582
|
+
/** The machine-readable reason for this failure. */
|
|
583
|
+
readonly code: FormErrorCode;
|
|
584
|
+
/** Structured values that locate or explain this failure. */
|
|
585
|
+
readonly context?: JSONRecord;
|
|
586
|
+
/**
|
|
587
|
+
* Create a form error.
|
|
588
|
+
*
|
|
589
|
+
* @param code - The machine-readable reason.
|
|
590
|
+
* @param message - The human-readable failure text.
|
|
591
|
+
* @param context - Optional structured failure details.
|
|
592
|
+
*/
|
|
593
|
+
constructor(code: FormErrorCode, message: string, context?: JSONRecord);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* The machine-readable code a form error carries.
|
|
598
|
+
*
|
|
599
|
+
* @remarks
|
|
600
|
+
* `SCHEMA` rejects a malformed schema. `FIELD` names a field the schema does not declare.
|
|
601
|
+
* `CONTROL` reports a value the field's control cannot hold. `SETTLED` and `ABANDONED`
|
|
602
|
+
* refuse a write to a form that has already ended.
|
|
603
|
+
*/
|
|
604
|
+
export declare type FormErrorCode = 'SCHEMA' | 'FIELD' | 'CONTROL' | 'SETTLED' | 'ABANDONED';
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Everything a form announces.
|
|
608
|
+
*
|
|
609
|
+
* @remarks
|
|
610
|
+
* `fill` carries the field that changed and its new value, where `undefined` is the value
|
|
611
|
+
* being cleared. `validate` carries the errors a check produced, empty when it found none.
|
|
612
|
+
* `disable` and `enable` each carry one field, and fire once per field whose state actually
|
|
613
|
+
* moved, in the order the schema declares them. `submit` fires only on a submit that passed.
|
|
614
|
+
* `clear` and `abandon` are signals.
|
|
615
|
+
*/
|
|
616
|
+
export declare type FormEventMap = {
|
|
617
|
+
readonly fill: readonly [name: string, value: FieldValue | undefined];
|
|
618
|
+
readonly validate: readonly [errors: readonly FieldError[]];
|
|
619
|
+
readonly disable: readonly [name: string];
|
|
620
|
+
readonly enable: readonly [name: string];
|
|
621
|
+
readonly submit: readonly [values: FormValues];
|
|
622
|
+
readonly clear: readonly [];
|
|
623
|
+
readonly abandon: readonly [];
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Any field a schema can declare.
|
|
628
|
+
*
|
|
629
|
+
* @remarks
|
|
630
|
+
* The union discriminates on `control`, so narrowing on that member reaches each variant's
|
|
631
|
+
* own options.
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```ts
|
|
635
|
+
* function choices(field: FormField): readonly FieldChoice[] {
|
|
636
|
+
* return field.control === 'select' || field.control === 'checkbox' ? field.choices : []
|
|
637
|
+
* }
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
export declare type FormField = TextField | EditorField | PasswordField | NumberField | DateField | TimeField | DatetimeField | ColorField | ConfirmField | SelectField | CheckboxField | FileField;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* A named section of a form.
|
|
644
|
+
*
|
|
645
|
+
* @remarks
|
|
646
|
+
* A field joins a group through {@link FieldBase.group}. Grouping arranges the form and
|
|
647
|
+
* changes no answer.
|
|
648
|
+
*/
|
|
649
|
+
export declare interface FormGroup {
|
|
650
|
+
readonly name: string;
|
|
651
|
+
readonly label: string;
|
|
652
|
+
readonly help?: string;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* A form: a schema, the answers given against it, and the errors they carry.
|
|
657
|
+
*
|
|
658
|
+
* @remarks
|
|
659
|
+
* `valid` is true when the last completed evaluation found no error, and `dirty` is true once
|
|
660
|
+
* an answer differs from the one the form opened with. Both are derived, never stored.
|
|
661
|
+
*
|
|
662
|
+
* `touched` holds the fields somebody has visited, which is what lets a renderer withhold an
|
|
663
|
+
* error until the person has had their turn at it.
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```ts
|
|
667
|
+
* form.fill('email', 'ada@example.com')
|
|
668
|
+
* const result = form.submit()
|
|
669
|
+
* if (result.success) await form.answer
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
export declare interface FormInterface {
|
|
673
|
+
/** The form's event emitter. */
|
|
674
|
+
readonly emitter: EmitterInterface<FormEventMap>;
|
|
675
|
+
/** The schema this form asks. */
|
|
676
|
+
readonly schema: FormSchema;
|
|
677
|
+
/** The answers held right now. */
|
|
678
|
+
readonly values: FormValues;
|
|
679
|
+
/**
|
|
680
|
+
* The answers the form opened with: the schema's defaults, overlaid with any seeded values.
|
|
681
|
+
*
|
|
682
|
+
* @remarks
|
|
683
|
+
* It is fixed when the form opens and never moves again, so it is what `dirty` measures
|
|
684
|
+
* against and what {@link FormInterface.clear} returns to.
|
|
685
|
+
*/
|
|
686
|
+
readonly baseline: FormValues;
|
|
687
|
+
/** Every error the last check produced. */
|
|
688
|
+
readonly errors: readonly FieldError[];
|
|
689
|
+
/** The names of the fields somebody has visited. */
|
|
690
|
+
readonly touched: ReadonlySet<string>;
|
|
691
|
+
/**
|
|
692
|
+
* The names of the fields currently out of the form.
|
|
693
|
+
*
|
|
694
|
+
* @remarks
|
|
695
|
+
* It opens as the set the schema declares through {@link FieldBase.disabled} and moves with
|
|
696
|
+
* every {@link FormInterface.disable} and {@link FormInterface.enable} call, so the schema
|
|
697
|
+
* holds the declaration and this holds the current fact.
|
|
698
|
+
*/
|
|
699
|
+
readonly disabled: ReadonlySet<string>;
|
|
700
|
+
/** Where the form sits in its life. */
|
|
701
|
+
readonly status: FormStatus;
|
|
702
|
+
/** Whether the last completed evaluation found no error. */
|
|
703
|
+
readonly valid: boolean;
|
|
704
|
+
/** Whether any answer has moved since the form opened. */
|
|
705
|
+
readonly dirty: boolean;
|
|
706
|
+
/**
|
|
707
|
+
* The answers, once the form settles.
|
|
708
|
+
*
|
|
709
|
+
* @remarks
|
|
710
|
+
* It resolves with the submitted values on the first valid submit, and rejects when teardown
|
|
711
|
+
* abandons the form before it settles.
|
|
712
|
+
*/
|
|
713
|
+
readonly answer: Promise<FormValues>;
|
|
714
|
+
/**
|
|
715
|
+
* Find one field by name.
|
|
716
|
+
*
|
|
717
|
+
* @param name - The field's name.
|
|
718
|
+
* @returns The field, or `undefined` when the schema declares no such name.
|
|
719
|
+
*/
|
|
720
|
+
field(name: string): FormField | undefined;
|
|
721
|
+
/**
|
|
722
|
+
* Answer several fields at once.
|
|
723
|
+
*
|
|
724
|
+
* @param values - The answers to write, each keyed by its field name.
|
|
725
|
+
*/
|
|
726
|
+
fill(values: FormValues): void;
|
|
727
|
+
/**
|
|
728
|
+
* Answer one field.
|
|
729
|
+
*
|
|
730
|
+
* @param name - The field's name.
|
|
731
|
+
* @param value - The answer to write, or `undefined` to clear it.
|
|
732
|
+
*/
|
|
733
|
+
fill(name: string, value: FieldValue | undefined): void;
|
|
734
|
+
/**
|
|
735
|
+
* Record that somebody has visited a field.
|
|
736
|
+
*
|
|
737
|
+
* @param name - The field's name.
|
|
738
|
+
*/
|
|
739
|
+
touch(name: string): void;
|
|
740
|
+
/**
|
|
741
|
+
* Fail a field from outside, for what the rules cannot see.
|
|
742
|
+
*
|
|
743
|
+
* @param name - The field's name.
|
|
744
|
+
* @param message - What to tell the person.
|
|
745
|
+
*/
|
|
746
|
+
invalidate(name: string, message: string): void;
|
|
747
|
+
/**
|
|
748
|
+
* Take every field out of the form.
|
|
749
|
+
*
|
|
750
|
+
* @remarks
|
|
751
|
+
* A disabled field is neither evaluated nor submitted. Its answer is kept, and so is any
|
|
752
|
+
* {@link FormInterface.invalidate} failure it carries, which is withheld from `errors` while
|
|
753
|
+
* the field is out and restored when it comes back. The form emits `disable` once per field
|
|
754
|
+
* whose state actually moved, in schema order, so a call that moves nothing announces
|
|
755
|
+
* nothing. Disabling is a write, so a settled or abandoned form refuses it.
|
|
756
|
+
*/
|
|
757
|
+
disable(): void;
|
|
758
|
+
/**
|
|
759
|
+
* Take one field out of the form.
|
|
760
|
+
*
|
|
761
|
+
* @param name - The field's name.
|
|
762
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name.
|
|
763
|
+
*/
|
|
764
|
+
disable(name: string): void;
|
|
765
|
+
/**
|
|
766
|
+
* Take several fields out of the form.
|
|
767
|
+
*
|
|
768
|
+
* @param names - The field names.
|
|
769
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name. Every name
|
|
770
|
+
* is checked before any field moves, so one bad name leaves the whole call undone.
|
|
771
|
+
*/
|
|
772
|
+
disable(names: readonly string[]): void;
|
|
773
|
+
/**
|
|
774
|
+
* Put every field back into the form.
|
|
775
|
+
*
|
|
776
|
+
* @remarks
|
|
777
|
+
* An enabled field is evaluated and submitted again, and any invalidation held while it was
|
|
778
|
+
* out reappears in `errors`. The form emits `enable` once per field whose state actually
|
|
779
|
+
* moved, in schema order. Enabling is a write, so a settled or abandoned form refuses it.
|
|
780
|
+
*/
|
|
781
|
+
enable(): void;
|
|
782
|
+
/**
|
|
783
|
+
* Put one field back into the form.
|
|
784
|
+
*
|
|
785
|
+
* @param name - The field's name.
|
|
786
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name.
|
|
787
|
+
*/
|
|
788
|
+
enable(name: string): void;
|
|
789
|
+
/**
|
|
790
|
+
* Put several fields back into the form.
|
|
791
|
+
*
|
|
792
|
+
* @param names - The field names.
|
|
793
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name. Every name
|
|
794
|
+
* is checked before any field moves, so one bad name leaves the whole call undone.
|
|
795
|
+
*/
|
|
796
|
+
enable(names: readonly string[]): void;
|
|
797
|
+
/**
|
|
798
|
+
* Check every answer and settle the form when they all pass.
|
|
799
|
+
*
|
|
800
|
+
* @returns The values on success, or every error that stopped them.
|
|
801
|
+
*/
|
|
802
|
+
submit(): FormResult;
|
|
803
|
+
/**
|
|
804
|
+
* Return every answer to {@link FormInterface.baseline}, the answers the form opened with.
|
|
805
|
+
*
|
|
806
|
+
* @remarks
|
|
807
|
+
* The runtime disabled overlay resets with them, so {@link FormInterface.disabled} reads the
|
|
808
|
+
* schema's declarations again.
|
|
809
|
+
*/
|
|
810
|
+
clear(): void;
|
|
811
|
+
/**
|
|
812
|
+
* Tear the form down, abandoning it when it has not settled.
|
|
813
|
+
*
|
|
814
|
+
* @remarks
|
|
815
|
+
* A request from inside a listener defers teardown until the outermost mutation batch closes,
|
|
816
|
+
* so an in-flight settlement can win and leave the form `settled` rather than `abandoned`.
|
|
817
|
+
*/
|
|
818
|
+
destroy(): void;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* How to open a form.
|
|
823
|
+
*
|
|
824
|
+
* @param options - The form's settings.
|
|
825
|
+
* @remarks
|
|
826
|
+
* `on` wires listeners at construction and `error` receives any throw from one of them.
|
|
827
|
+
* `values` seeds the answers, overriding each field's declared default. `messages` replaces
|
|
828
|
+
* the default message of a rule, keyed by {@link FieldRuleName}.
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* ```ts
|
|
832
|
+
* const options: FormOptions = {
|
|
833
|
+
* values: { email: 'ada@example.com' },
|
|
834
|
+
* messages: { required: 'This one is needed' },
|
|
835
|
+
* on: { submit: (values) => save(values) },
|
|
836
|
+
* }
|
|
837
|
+
* ```
|
|
838
|
+
*/
|
|
839
|
+
export declare interface FormOptions {
|
|
840
|
+
readonly on?: EmitterHooks<FormEventMap>;
|
|
841
|
+
readonly error?: EmitterErrorHandler;
|
|
842
|
+
readonly values?: FormValues;
|
|
843
|
+
readonly messages?: Readonly<Partial<Record<FieldRuleName, string>>>;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* What a submit answers with: the values, or every error that stopped them.
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```ts
|
|
851
|
+
* const result: FormResult = form.submit()
|
|
852
|
+
* const email = result.success ? result.value.email : undefined
|
|
853
|
+
* ```
|
|
854
|
+
*/
|
|
855
|
+
export declare type FormResult = Result<FormValues, readonly FieldError[]>;
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Everything a form asks.
|
|
859
|
+
*
|
|
860
|
+
* @remarks
|
|
861
|
+
* `fields` is the schema's only required member, and the order it declares is the order the
|
|
862
|
+
* form presents. `name`, `label`, and `help` describe the form itself.
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* ```ts
|
|
866
|
+
* const schema: FormSchema = {
|
|
867
|
+
* label: 'Sign up',
|
|
868
|
+
* fields: [
|
|
869
|
+
* { control: 'text', name: 'email', label: 'Email', rule: { required: true, email: true } },
|
|
870
|
+
* { control: 'confirm', name: 'terms', label: 'I accept the terms' },
|
|
871
|
+
* ],
|
|
872
|
+
* }
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
export declare interface FormSchema {
|
|
876
|
+
readonly name?: string;
|
|
877
|
+
readonly label?: string;
|
|
878
|
+
readonly help?: string;
|
|
879
|
+
readonly groups?: readonly FormGroup[];
|
|
880
|
+
readonly fields: readonly FormField[];
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Where a form sits in its life.
|
|
885
|
+
*
|
|
886
|
+
* @remarks
|
|
887
|
+
* A form opens `editing`, turns `settled` on its first valid submit, and turns `abandoned`
|
|
888
|
+
* when it is destroyed before settling. Both end states are terminal, and a write to a form
|
|
889
|
+
* in either one is refused. A destroy requested while a mutation batch is open is recorded,
|
|
890
|
+
* refuses every subsequent write immediately, and defers teardown until the outermost batch
|
|
891
|
+
* closes. The batch outcome wins: a batch that settles the form leaves it `settled`, resolves
|
|
892
|
+
* `answer`, and emits no `abandon`. Teardown never advances into, aborts, or rolls back the
|
|
893
|
+
* batch. The pending request is private, unnamed state and adds no fourth status.
|
|
894
|
+
*/
|
|
895
|
+
export declare type FormStatus = 'editing' | 'settled' | 'abandoned';
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* A form's answers, keyed by field name.
|
|
899
|
+
*
|
|
900
|
+
* @remarks
|
|
901
|
+
* A name with no key is a field nobody has answered. A disabled field's value may appear so a
|
|
902
|
+
* renderer can show it, but the value is never evaluated or submitted.
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```ts
|
|
906
|
+
* const values: FormValues = { email: 'ada@example.com', terms: true }
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
export declare type FormValues = Readonly<Record<string, FieldValue>>;
|
|
910
|
+
|
|
911
|
+
/** The maximum number of groups one schema may declare. */
|
|
912
|
+
export declare const GROUP_LIMIT = 64;
|
|
913
|
+
|
|
914
|
+
/** A signed or unsigned base-ten integer string. */
|
|
915
|
+
export declare const INTEGER_PATTERN: Readonly<RegExp>;
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Determine whether an unknown value is one exact field choice record.
|
|
919
|
+
*
|
|
920
|
+
* @param input - The value to inspect.
|
|
921
|
+
* @returns Whether the value is a field choice.
|
|
922
|
+
*/
|
|
923
|
+
export declare function isFieldChoice(input: unknown): input is FieldChoice;
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Determine whether an unknown value is a declared field control.
|
|
927
|
+
*
|
|
928
|
+
* @param input - The value to inspect.
|
|
929
|
+
* @returns Whether the value is a field control.
|
|
930
|
+
*/
|
|
931
|
+
export declare function isFieldControl(input: unknown): input is FieldControl;
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Determine whether an unknown value is one exact field error record.
|
|
935
|
+
*
|
|
936
|
+
* @param input - The value to inspect.
|
|
937
|
+
* @returns Whether the value is a field error.
|
|
938
|
+
*/
|
|
939
|
+
export declare function isFieldError(input: unknown): input is FieldError;
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Determine whether an unknown value is one exact field rule record.
|
|
943
|
+
*
|
|
944
|
+
* @param input - The value to inspect.
|
|
945
|
+
* @returns Whether the value is a structurally valid field rule.
|
|
946
|
+
*/
|
|
947
|
+
export declare function isFieldRule(input: unknown): input is FieldRule;
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Determine whether an unknown value has a form field value shape.
|
|
951
|
+
*
|
|
952
|
+
* @param input - The value to inspect.
|
|
953
|
+
* @returns Whether the value is a field value.
|
|
954
|
+
*/
|
|
955
|
+
export declare function isFieldValue(input: unknown): input is FieldValue;
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Determine whether an unknown value is a form error.
|
|
959
|
+
*
|
|
960
|
+
* @param input - The value to inspect.
|
|
961
|
+
* @returns Whether the value is a {@link FormError} instance.
|
|
962
|
+
*/
|
|
963
|
+
export declare function isFormError(input: unknown): input is FormError;
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Determine whether an unknown value is one exact discriminated form field.
|
|
967
|
+
*
|
|
968
|
+
* @remarks
|
|
969
|
+
* Metadata is admitted structurally as bounded JSON. An accessor-bearing metadata record is
|
|
970
|
+
* refused later when {@link cloneFormField} takes ownership, because ownership accepts enumerable
|
|
971
|
+
* data properties only.
|
|
972
|
+
*
|
|
973
|
+
* @param input - The value to inspect.
|
|
974
|
+
* @returns Whether the value is a structurally valid form field.
|
|
975
|
+
*/
|
|
976
|
+
export declare function isFormField(input: unknown): input is FormField;
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Determine whether an unknown value is one exact form group record.
|
|
980
|
+
*
|
|
981
|
+
* @param input - The value to inspect.
|
|
982
|
+
* @returns Whether the value is a form group.
|
|
983
|
+
*/
|
|
984
|
+
export declare function isFormGroup(input: unknown): input is FormGroup;
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Determine whether an unknown value is one exact structural form schema.
|
|
988
|
+
*
|
|
989
|
+
* @param input - The value to inspect.
|
|
990
|
+
* @returns Whether the value is a structurally valid form schema.
|
|
991
|
+
*/
|
|
992
|
+
export declare function isFormSchema(input: unknown): input is FormSchema;
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Determine whether an unknown value is a form lifecycle status.
|
|
996
|
+
*
|
|
997
|
+
* @param input - The value to inspect.
|
|
998
|
+
* @returns Whether the value is a form status.
|
|
999
|
+
*/
|
|
1000
|
+
export declare function isFormStatus(input: unknown): input is FormStatus;
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Determine whether an unknown value is a record of field values.
|
|
1004
|
+
*
|
|
1005
|
+
* @param input - The value to inspect.
|
|
1006
|
+
* @returns Whether the value is a form values record.
|
|
1007
|
+
*/
|
|
1008
|
+
export declare function isFormValues(input: unknown): input is FormValues;
|
|
1009
|
+
|
|
1010
|
+
/** The maximum number of entries one list-valued answer may hold. */
|
|
1011
|
+
export declare const LIST_LIMIT = 1024;
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Decide whether a raw binding value projects to an answered field.
|
|
1015
|
+
*
|
|
1016
|
+
* @remarks
|
|
1017
|
+
* Bind with `fill(name, matchesAnswer(raw) ? raw : undefined)`. This projection treats an absent
|
|
1018
|
+
* value and a string containing only whitespace as unanswered. Every other field value is an
|
|
1019
|
+
* answer, including an empty list, `false`, and zero. Core evaluation does not use this projection:
|
|
1020
|
+
* its `required` rule remains presence-only.
|
|
1021
|
+
*
|
|
1022
|
+
* @param value - The raw field value, or absence.
|
|
1023
|
+
* @returns Whether the binding should preserve the value as an answer.
|
|
1024
|
+
*/
|
|
1025
|
+
export declare function matchesAnswer(value: FieldValue | undefined): boolean;
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Check whether a value has the shape required by one field control.
|
|
1029
|
+
*
|
|
1030
|
+
* @param field - The field that owns the value.
|
|
1031
|
+
* @param value - The unknown value to inspect.
|
|
1032
|
+
* @returns Whether the control can hold the value.
|
|
1033
|
+
*/
|
|
1034
|
+
export declare function matchesField(field: FormField, value: unknown): value is FieldValue;
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Compare two field values by scalar identity or ordered list content.
|
|
1038
|
+
*
|
|
1039
|
+
* @param a - The first field value.
|
|
1040
|
+
* @param b - The second field value.
|
|
1041
|
+
* @returns Whether both values contain the same answer.
|
|
1042
|
+
*/
|
|
1043
|
+
export declare function matchesValue(a: FieldValue, b: FieldValue): boolean;
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Compare two form value records by keys and value content.
|
|
1047
|
+
*
|
|
1048
|
+
* @param a - The first value record.
|
|
1049
|
+
* @param b - The second value record.
|
|
1050
|
+
* @returns Whether both records contain the same answers.
|
|
1051
|
+
*/
|
|
1052
|
+
export declare function matchesValues(a: FormValues, b: FormValues): boolean;
|
|
1053
|
+
|
|
1054
|
+
/** The maximum length, in UTF-16 code units, of a schema, group, or field name. */
|
|
1055
|
+
export declare const NAME_LIMIT = 128;
|
|
1056
|
+
|
|
1057
|
+
/** The maximum total number of records, arrays, and leaves one schema retains. */
|
|
1058
|
+
export declare const NODE_LIMIT = 16384;
|
|
1059
|
+
|
|
1060
|
+
/** A number. */
|
|
1061
|
+
export declare interface NumberField extends FieldBase {
|
|
1062
|
+
readonly control: 'number';
|
|
1063
|
+
readonly default?: number;
|
|
1064
|
+
readonly placeholder?: string;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Parse unknown wire data into an owned, semantically sound form schema.
|
|
1069
|
+
*
|
|
1070
|
+
* @param input - The unknown schema value to parse.
|
|
1071
|
+
* @returns An owned schema with custom rules removed, or `undefined` on refusal.
|
|
1072
|
+
*/
|
|
1073
|
+
export declare function parseForm(input: unknown): FormSchema | undefined;
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Parse one answer against its field control.
|
|
1077
|
+
*
|
|
1078
|
+
* @param field - The field that defines the accepted value.
|
|
1079
|
+
* @param input - The unknown value to parse.
|
|
1080
|
+
* @returns The typed or lexically coerced field value, or `undefined` on refusal.
|
|
1081
|
+
*/
|
|
1082
|
+
export declare function parseValue(field: FormField, input: unknown): FieldValue | undefined;
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Parse a strict answer record against the fields declared by a schema.
|
|
1086
|
+
*
|
|
1087
|
+
* @param schema - The schema that owns the accepted field names and controls.
|
|
1088
|
+
* @param input - The unknown answer record to parse.
|
|
1089
|
+
* @returns An owned answer record, or `undefined` when any key or value is refused.
|
|
1090
|
+
*/
|
|
1091
|
+
export declare function parseValues(schema: FormSchema, input: unknown): FormValues | undefined;
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* A secret, obscured as it is typed.
|
|
1095
|
+
*
|
|
1096
|
+
* @remarks
|
|
1097
|
+
* It carries no `default` deliberately: a seeded secret is a secret written down. `mask` is
|
|
1098
|
+
* the character the control repeats in place of the text.
|
|
1099
|
+
*/
|
|
1100
|
+
export declare interface PasswordField extends FieldBase {
|
|
1101
|
+
readonly control: 'password';
|
|
1102
|
+
readonly mask?: string;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/** The maximum accepted source length for an authored regular expression. */
|
|
1106
|
+
export declare const PATTERN_LIMIT = 256;
|
|
1107
|
+
|
|
1108
|
+
/** Default failure copy for every named field rule. */
|
|
1109
|
+
export declare const RULE_MESSAGES: Readonly<Record<FieldRuleName, string>>;
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* One choice out of a list.
|
|
1113
|
+
*
|
|
1114
|
+
* @remarks
|
|
1115
|
+
* `open` admits a value the list does not offer, which is what turns a closed menu into a
|
|
1116
|
+
* suggestion list.
|
|
1117
|
+
*/
|
|
1118
|
+
export declare interface SelectField extends FieldBase {
|
|
1119
|
+
readonly control: 'select';
|
|
1120
|
+
readonly choices: readonly FieldChoice[];
|
|
1121
|
+
readonly default?: string;
|
|
1122
|
+
readonly open?: boolean;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Project a schema into JSON while removing custom validators and absent values.
|
|
1127
|
+
*
|
|
1128
|
+
* @param schema - The schema to project.
|
|
1129
|
+
* @returns A deep JSON copy of the serializable schema.
|
|
1130
|
+
* @throws A {@link FormError} coded `SCHEMA` when accessor-bearing metadata cannot be owned. A
|
|
1131
|
+
* non-contract throw while reading metadata escapes unchanged.
|
|
1132
|
+
*/
|
|
1133
|
+
export declare function serializeForm(schema: FormSchema): JSONRecord;
|
|
1134
|
+
|
|
1135
|
+
/** The maximum length, in UTF-16 code units, of any single retained string. */
|
|
1136
|
+
export declare const STRING_LIMIT = 65536;
|
|
1137
|
+
|
|
1138
|
+
/** The maximum total length, in UTF-16 code units, of every string one schema retains. */
|
|
1139
|
+
export declare const TEXT_LIMIT = 1048576;
|
|
1140
|
+
|
|
1141
|
+
/** A single line of text. */
|
|
1142
|
+
export declare interface TextField extends FieldBase {
|
|
1143
|
+
readonly control: 'text';
|
|
1144
|
+
readonly default?: string;
|
|
1145
|
+
readonly placeholder?: string;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** A 24-hour time string with optional seconds. */
|
|
1149
|
+
export declare const TIME_PATTERN: Readonly<RegExp>;
|
|
1150
|
+
|
|
1151
|
+
/** A time of day, held as the control's own string. */
|
|
1152
|
+
export declare interface TimeField extends FieldBase {
|
|
1153
|
+
readonly control: 'time';
|
|
1154
|
+
readonly default?: string;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/** An absolute HTTP or HTTPS URL shape. */
|
|
1158
|
+
export declare const URL_PATTERN: Readonly<RegExp>;
|
|
1159
|
+
|
|
1160
|
+
export { }
|