@orkestrel/form 0.0.9 → 0.0.11
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 +7 -5
- package/dist/src/browser/index.d.ts +750 -0
- package/dist/src/browser/index.js +1056 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/core/index.cjs +183 -17
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +95 -15
- package/dist/src/core/index.d.ts +95 -15
- package/dist/src/core/index.js +183 -17
- package/dist/src/core/index.js.map +1 -1
- package/package.json +27 -14
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
import type { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
2
|
+
import type { EmitterHooks } from '@orkestrel/emitter';
|
|
3
|
+
import type { EmitterInterface } from '@orkestrel/emitter';
|
|
4
|
+
import type { JSONRecord } from '@orkestrel/contract';
|
|
5
|
+
import type { Result } from '@orkestrel/contract';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents any number of choices out of a list, holding the checked values.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* A field offering one box that means yes or no is a {@link ConfirmField}, not a one-choice
|
|
12
|
+
* checkbox.
|
|
13
|
+
*/
|
|
14
|
+
declare interface CheckboxField extends FieldBase {
|
|
15
|
+
readonly control: 'checkbox';
|
|
16
|
+
readonly choices: readonly FieldChoice[];
|
|
17
|
+
readonly default?: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Represents a color, held as the control's own six-digit `#rrggbb` string. */
|
|
21
|
+
declare interface ColorField extends FieldBase {
|
|
22
|
+
readonly control: 'color';
|
|
23
|
+
readonly default?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Represents a single on/off box, holding a boolean. */
|
|
27
|
+
declare interface ConfirmField extends FieldBase {
|
|
28
|
+
readonly control: 'confirm';
|
|
29
|
+
readonly default?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a native form binding exposed through its lifecycle interface.
|
|
34
|
+
* @param element - The native form.
|
|
35
|
+
* @param document - The borrowed answer document.
|
|
36
|
+
* @param options - Feedback destinations and listeners.
|
|
37
|
+
* @returns The initialized native binding.
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const binding = createFormBinding(element, document)
|
|
41
|
+
* binding.refresh()
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function createFormBinding(element: HTMLFormElement, document: FormInterface, options?: FormBindingOptions): FormBindingInterface;
|
|
45
|
+
|
|
46
|
+
/** Represents a calendar date, held as the control's own `YYYY-MM-DD` string. */
|
|
47
|
+
declare interface DateField extends FieldBase {
|
|
48
|
+
readonly control: 'date';
|
|
49
|
+
readonly default?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Represents a date and a time of day together, with no zone, held as the control's own string. */
|
|
53
|
+
declare interface DatetimeField extends FieldBase {
|
|
54
|
+
readonly control: 'datetime';
|
|
55
|
+
readonly default?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Represents text over many lines. */
|
|
59
|
+
declare interface EditorField extends FieldBase {
|
|
60
|
+
readonly control: 'editor';
|
|
61
|
+
readonly default?: string;
|
|
62
|
+
readonly placeholder?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Declares what every field carries, whatever its control.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* `name` keys the field in {@link FormValues} and `group` names a {@link FormGroup}.
|
|
70
|
+
*
|
|
71
|
+
* `hidden`, `locked`, and `disabled` differ in what they remove. `hidden` keeps the field out of
|
|
72
|
+
* the rendered form, `locked` renders it unwritable, and both are still validated and still
|
|
73
|
+
* submitted. `disabled` takes the field out of the form entirely: it is neither validated nor
|
|
74
|
+
* submitted. It is the field's declared, opening state; {@link FormInterface.disabled} is the
|
|
75
|
+
* current fact, because a live form can move a field either way.
|
|
76
|
+
*
|
|
77
|
+
* `meta` is a bounded JSON carrier for whatever the schema declines to model. Evaluation never
|
|
78
|
+
* reads it, no rule sees it, and it round-trips verbatim through serialization. This package
|
|
79
|
+
* defines no key in it, so every key belongs to the host.
|
|
80
|
+
*/
|
|
81
|
+
declare interface FieldBase {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly label?: string;
|
|
84
|
+
readonly help?: string;
|
|
85
|
+
readonly group?: string;
|
|
86
|
+
readonly hidden?: boolean;
|
|
87
|
+
readonly disabled?: boolean;
|
|
88
|
+
readonly locked?: boolean;
|
|
89
|
+
readonly rule?: FieldRule;
|
|
90
|
+
readonly meta?: JSONRecord;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Represents one option a `select` or `checkbox` field offers.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* `value` is what the form stores and `label` is what the person reads. `help` explains the
|
|
98
|
+
* option, and `disabled` shows it while refusing its value at every input door, including
|
|
99
|
+
* seeded values.
|
|
100
|
+
*/
|
|
101
|
+
declare interface FieldChoice {
|
|
102
|
+
readonly value: string;
|
|
103
|
+
readonly label: string;
|
|
104
|
+
readonly help?: string;
|
|
105
|
+
readonly disabled?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Represents one failed check against one field.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* `rule` names the constraint that failed. It is absent when the message came from a
|
|
113
|
+
* {@link FieldRule.custom} validator or from {@link FormInterface.invalidate}, because
|
|
114
|
+
* neither failure belongs to a named rule.
|
|
115
|
+
*/
|
|
116
|
+
declare interface FieldError {
|
|
117
|
+
readonly field: string;
|
|
118
|
+
readonly message: string;
|
|
119
|
+
readonly rule?: FieldRuleName;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Represents the constraints one field's value must satisfy.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* `minimum` and `maximum` measure whatever the control makes countable: characters for
|
|
127
|
+
* `text`, `editor`, and `password`; magnitude for `number`; chronology for `date`, `time`,
|
|
128
|
+
* and `datetime`, whose operand is a string written in that control's own format; and the
|
|
129
|
+
* number of selections for `checkbox` and `file`.
|
|
130
|
+
*
|
|
131
|
+
* `step` is the interval a numeric value must land on. `pattern` is regular-expression
|
|
132
|
+
* source. `email`, `url`, `integer`, and `alphanumeric` each assert one shape over the whole
|
|
133
|
+
* value. `custom` runs last and is the only rule that sees the rest of the form.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const rule: FieldRule = { required: true, minimum: 8, pattern: '\\d' }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
declare interface FieldRule {
|
|
141
|
+
readonly required?: boolean;
|
|
142
|
+
readonly minimum?: number | string;
|
|
143
|
+
readonly maximum?: number | string;
|
|
144
|
+
readonly step?: number;
|
|
145
|
+
readonly pattern?: string;
|
|
146
|
+
readonly email?: boolean;
|
|
147
|
+
readonly url?: boolean;
|
|
148
|
+
readonly integer?: boolean;
|
|
149
|
+
readonly alphanumeric?: boolean;
|
|
150
|
+
readonly custom?: FieldValidator;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Lists every rule that reports its failure by name.
|
|
155
|
+
*
|
|
156
|
+
* @remarks
|
|
157
|
+
* `custom` is excluded because it supplies its own message, so nothing keyed by a rule name
|
|
158
|
+
* would ever be read for it. {@link FormOptions.messages} is keyed by this name.
|
|
159
|
+
*/
|
|
160
|
+
declare type FieldRuleName = Exclude<keyof FieldRule, 'custom'>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks one value against the whole form.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* It runs after every named rule, and it runs on an absent value as well as a present one. That
|
|
167
|
+
* is what makes a rule such as "required once the sibling says yes" expressible, and it means an
|
|
168
|
+
* unanswered field can carry both a `required` message and this validator's own.
|
|
169
|
+
*
|
|
170
|
+
* @param value - The value the field holds, or `undefined` when nobody has answered it.
|
|
171
|
+
* @param values - Every answer the form holds, so a rule can read its siblings.
|
|
172
|
+
* @returns `true` when the value passes, or the message explaining why it failed.
|
|
173
|
+
* @throws Thrown when a {@link FieldValidator} throws; its own thrown value escapes the mutation
|
|
174
|
+
* call unchanged. When a form mutation has already changed state, the throw leaves those
|
|
175
|
+
* changes beside the error list from before that mutation. An invalidation can therefore be
|
|
176
|
+
* recorded without appearing in `errors`, and a clear can reset state without emitting `clear`.
|
|
177
|
+
* Form-owned refusals use
|
|
178
|
+
* {@link FormError} instead.
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const matches: FieldValidator = (value, values) =>
|
|
182
|
+
* value === values.password ? true : 'Both passwords must match'
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare type FieldValidator = (value: FieldValue | undefined, values: FormValues) => true | string;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Represents every value a field can hold.
|
|
189
|
+
*
|
|
190
|
+
* @remarks
|
|
191
|
+
* The variant follows the control: text-like controls hold a `string`, `number` holds a
|
|
192
|
+
* `number`, `confirm` holds a `boolean`, and `checkbox` holds its checked values as a
|
|
193
|
+
* `readonly string[]`.
|
|
194
|
+
*/
|
|
195
|
+
declare type FieldValue = string | number | boolean | readonly string[];
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Represents one or more files, by name.
|
|
199
|
+
*
|
|
200
|
+
* @remarks
|
|
201
|
+
* A value is the names alone: bytes never enter the document. `accept` lists the media types
|
|
202
|
+
* and extensions the control offers, in the form the host expects.
|
|
203
|
+
*/
|
|
204
|
+
declare interface FileField extends FieldBase {
|
|
205
|
+
readonly control: 'file';
|
|
206
|
+
readonly accept?: readonly string[];
|
|
207
|
+
readonly multiple?: boolean;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Implements a controlled native form over a borrowed document. */
|
|
211
|
+
export declare class FormBinding implements FormBindingInterface {
|
|
212
|
+
#private;
|
|
213
|
+
/**
|
|
214
|
+
* Binds existing controls to the supplied document's answers.
|
|
215
|
+
* @param element - The native form, including its externally associated controls.
|
|
216
|
+
* @param document - The borrowed answer document.
|
|
217
|
+
* @param options - Feedback destinations and listeners.
|
|
218
|
+
* @throws Thrown when controls contradict the schema or native initialization fails.
|
|
219
|
+
*/
|
|
220
|
+
constructor(element: HTMLFormElement, document: FormInterface, options?: FormBindingOptions);
|
|
221
|
+
/** Holds the native form. */
|
|
222
|
+
get element(): HTMLFormElement;
|
|
223
|
+
/** Holds the borrowed answer document. */
|
|
224
|
+
get document(): FormInterface;
|
|
225
|
+
/** Reports whether the binding released its resources. */
|
|
226
|
+
get destroyed(): boolean;
|
|
227
|
+
/** Holds the binding's isolated event emitter. */
|
|
228
|
+
get emitter(): EmitterInterface<FormBindingEventMap>;
|
|
229
|
+
/** Imports mounted enabled controls without acknowledging their answers. */
|
|
230
|
+
read(): void;
|
|
231
|
+
/** Rediscovers controls and projects the document into the native form. */
|
|
232
|
+
refresh(): void;
|
|
233
|
+
/** Releases owned DOM effects and listeners without destroying the document. */
|
|
234
|
+
destroy(): void;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Records an attribute's authored value and the binding's last contribution. */
|
|
238
|
+
export declare interface FormBindingAttribute {
|
|
239
|
+
readonly original: string | null;
|
|
240
|
+
readonly value: string | null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Reports an incompatible control, overlapping submission, or native DOM failure. */
|
|
244
|
+
export declare class FormBindingError extends Error {
|
|
245
|
+
readonly code: FormBindingErrorCode;
|
|
246
|
+
readonly context: Readonly<Record<string, unknown>> | undefined;
|
|
247
|
+
/**
|
|
248
|
+
* Creates a native binding refusal.
|
|
249
|
+
* @param code - The refusal category.
|
|
250
|
+
* @param message - The explanation.
|
|
251
|
+
* @param context - The associated field or cause.
|
|
252
|
+
*/
|
|
253
|
+
constructor(code: FormBindingErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Names a native binding refusal. */
|
|
257
|
+
export declare type FormBindingErrorCode = 'CONTROL' | 'PENDING' | 'DOM';
|
|
258
|
+
|
|
259
|
+
/** Lists the native binding's notifications after their associated DOM effects. */
|
|
260
|
+
export declare type FormBindingEventMap = {
|
|
261
|
+
readonly render: readonly [];
|
|
262
|
+
/** Reports a control after its native value changes during document projection. */
|
|
263
|
+
readonly write: readonly [control: FormControl];
|
|
264
|
+
readonly submit: readonly [submission: FormSubmission, submitter: HTMLElement | undefined];
|
|
265
|
+
readonly error: readonly [error: unknown];
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/** Declares a native form's borrowed document and synchronization lifecycle. */
|
|
269
|
+
export declare interface FormBindingInterface {
|
|
270
|
+
readonly element: HTMLFormElement;
|
|
271
|
+
readonly document: FormInterface;
|
|
272
|
+
readonly destroyed: boolean;
|
|
273
|
+
readonly emitter: EmitterInterface<FormBindingEventMap>;
|
|
274
|
+
/** Imports mounted enabled controls without acknowledging their answers. */
|
|
275
|
+
read(): void;
|
|
276
|
+
/** Rediscovers controls and projects the document into the native form. */
|
|
277
|
+
refresh(): void;
|
|
278
|
+
/** Releases owned DOM effects and listeners without destroying the document. */
|
|
279
|
+
destroy(): void;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Configures neutral feedback destinations and isolated event listeners. */
|
|
283
|
+
export declare interface FormBindingOptions {
|
|
284
|
+
readonly feedback?: ReadonlyMap<string, HTMLElement>;
|
|
285
|
+
readonly summary?: HTMLElement;
|
|
286
|
+
/** Maps native controls to focus targets whose departure touches the represented field. */
|
|
287
|
+
readonly presentations?: ReadonlyMap<FormControl, HTMLElement>;
|
|
288
|
+
/** Defers blur touch until a same-form button's primary pointer press finishes. Default: false. */
|
|
289
|
+
readonly defer?: boolean;
|
|
290
|
+
readonly on?: EmitterHooks<FormBindingEventMap>;
|
|
291
|
+
readonly error?: EmitterErrorHandler;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Records the binding's custom-validity contribution beside the authored message. */
|
|
295
|
+
export declare interface FormBindingValidity {
|
|
296
|
+
readonly original: string;
|
|
297
|
+
readonly value: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Represents a native answer control admitted by the binding. */
|
|
301
|
+
export declare type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Lists everything a form announces.
|
|
305
|
+
*
|
|
306
|
+
* @remarks
|
|
307
|
+
* `fill` carries the field that changed and its new value, where `undefined` is the value
|
|
308
|
+
* being cleared. `validate` carries the errors a check produced, empty when it found none.
|
|
309
|
+
* `disable` and `enable` each carry one field, and fire once per field whose state actually
|
|
310
|
+
* moved, in the order the schema declares them. `submit` fires only on a submit that passed.
|
|
311
|
+
* `touch` announces the first visit since clear. `prepare`, `accept`, and `abort` carry the receipt
|
|
312
|
+
* after their state is committed. `clear` and `abandon` are signals.
|
|
313
|
+
*/
|
|
314
|
+
declare type FormEventMap = {
|
|
315
|
+
readonly fill: readonly [name: string, value: FieldValue | undefined];
|
|
316
|
+
readonly touch: readonly [name: string];
|
|
317
|
+
readonly validate: readonly [errors: readonly FieldError[]];
|
|
318
|
+
readonly disable: readonly [name: string];
|
|
319
|
+
readonly enable: readonly [name: string];
|
|
320
|
+
readonly submit: readonly [values: FormValues];
|
|
321
|
+
readonly prepare: readonly [submission: FormSubmission];
|
|
322
|
+
readonly accept: readonly [submission: FormSubmission];
|
|
323
|
+
readonly abort: readonly [submission: FormSubmission];
|
|
324
|
+
readonly clear: readonly [];
|
|
325
|
+
readonly abandon: readonly [];
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Represents any field a schema can declare.
|
|
330
|
+
*
|
|
331
|
+
* @remarks
|
|
332
|
+
* The union discriminates on `control`, so narrowing on that member reaches each variant's
|
|
333
|
+
* own options.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* function choices(field: FormField): readonly FieldChoice[] {
|
|
338
|
+
* return field.control === 'select' || field.control === 'checkbox' ? field.choices : []
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
declare type FormField = TextField | EditorField | PasswordField | NumberField | DateField | TimeField | DatetimeField | ColorField | ConfirmField | SelectField | CheckboxField | FileField;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Represents a named section of a form.
|
|
346
|
+
*
|
|
347
|
+
* @remarks
|
|
348
|
+
* A field joins a group through {@link FieldBase.group}. Grouping arranges the form and
|
|
349
|
+
* changes no answer.
|
|
350
|
+
*/
|
|
351
|
+
declare interface FormGroup {
|
|
352
|
+
readonly name: string;
|
|
353
|
+
readonly label: string;
|
|
354
|
+
readonly help?: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Declares the contract a form exposes: the state it holds and the calls that move it.
|
|
359
|
+
*
|
|
360
|
+
* @remarks
|
|
361
|
+
* `valid` is true when the last completed evaluation found no error, and `dirty` is true once
|
|
362
|
+
* an answer differs from its acknowledged baseline. Both are derived, never stored.
|
|
363
|
+
*
|
|
364
|
+
* `touched` holds the fields somebody has visited, which is what lets a renderer withhold an
|
|
365
|
+
* error until the person has had their turn at it.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* form.fill('email', 'ada@example.com')
|
|
370
|
+
* const result = form.submit()
|
|
371
|
+
* if (result.success) await form.answer
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
declare interface FormInterface {
|
|
375
|
+
/** Holds the form's event emitter. */
|
|
376
|
+
readonly emitter: EmitterInterface<FormEventMap>;
|
|
377
|
+
/** Holds the schema this form asks. */
|
|
378
|
+
readonly schema: FormSchema;
|
|
379
|
+
/** Reports the answers the form holds. */
|
|
380
|
+
readonly values: FormValues;
|
|
381
|
+
/**
|
|
382
|
+
* Holds the acknowledged answers, initially the schema's defaults overlaid with seeded values.
|
|
383
|
+
*
|
|
384
|
+
* @remarks
|
|
385
|
+
* Acceptance advances only the enabled fields captured by the submission, including absence.
|
|
386
|
+
* It is what `dirty` measures against and what {@link FormInterface.clear} returns to.
|
|
387
|
+
*/
|
|
388
|
+
readonly baseline: FormValues;
|
|
389
|
+
/** Holds the submission awaiting acknowledgement, or `undefined` when none is pending. */
|
|
390
|
+
readonly pending: FormSubmission | undefined;
|
|
391
|
+
/** Holds every error the last check produced. */
|
|
392
|
+
readonly errors: readonly FieldError[];
|
|
393
|
+
/** Lists the names of the fields somebody has visited. */
|
|
394
|
+
readonly touched: ReadonlySet<string>;
|
|
395
|
+
/**
|
|
396
|
+
* Lists the names of the fields that are out of the form.
|
|
397
|
+
*
|
|
398
|
+
* @remarks
|
|
399
|
+
* It opens as the set the schema declares through {@link FieldBase.disabled} and moves with
|
|
400
|
+
* every {@link FormInterface.disable} and {@link FormInterface.enable} call, so the schema
|
|
401
|
+
* holds the declaration and this holds the current fact.
|
|
402
|
+
*/
|
|
403
|
+
readonly disabled: ReadonlySet<string>;
|
|
404
|
+
/** Reports where the form sits in its life. */
|
|
405
|
+
readonly status: FormStatus;
|
|
406
|
+
/** Reports whether the last completed evaluation found no error. */
|
|
407
|
+
readonly valid: boolean;
|
|
408
|
+
/** Reports whether any answer differs from its acknowledged baseline. */
|
|
409
|
+
readonly dirty: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Holds the answers after the form settles.
|
|
412
|
+
*
|
|
413
|
+
* @remarks
|
|
414
|
+
* It resolves with the submitted values on the first valid submit, and rejects when teardown
|
|
415
|
+
* abandons the form before it settles.
|
|
416
|
+
*/
|
|
417
|
+
readonly answer: Promise<FormValues>;
|
|
418
|
+
/**
|
|
419
|
+
* Finds one field by name.
|
|
420
|
+
*
|
|
421
|
+
* @param name - The field's name.
|
|
422
|
+
* @returns The field, or `undefined` when the schema declares no such name.
|
|
423
|
+
*/
|
|
424
|
+
field(name: string): FormField | undefined;
|
|
425
|
+
/**
|
|
426
|
+
* Answers one field, or several at once.
|
|
427
|
+
*
|
|
428
|
+
* @param values - The answers to write, each keyed by its field name.
|
|
429
|
+
*/
|
|
430
|
+
fill(values: FormValues): void;
|
|
431
|
+
/**
|
|
432
|
+
* Answers one field.
|
|
433
|
+
*
|
|
434
|
+
* @param name - The field's name.
|
|
435
|
+
* @param value - The answer to write, or `undefined` to clear it.
|
|
436
|
+
*/
|
|
437
|
+
fill(name: string, value: FieldValue | undefined): void;
|
|
438
|
+
/**
|
|
439
|
+
* Records that somebody has visited a field.
|
|
440
|
+
*
|
|
441
|
+
* @param name - The field's name.
|
|
442
|
+
*/
|
|
443
|
+
touch(name: string): void;
|
|
444
|
+
/**
|
|
445
|
+
* Fails a field from outside, for what the rules cannot see.
|
|
446
|
+
*
|
|
447
|
+
* @remarks
|
|
448
|
+
* One field holds one external failure, so a second call replaces the first, and the failure
|
|
449
|
+
* lasts until that field is filled again or the form is cleared.
|
|
450
|
+
* A receipt admits only a captured enabled field whose fill revision still matches. Stale,
|
|
451
|
+
* foreign, copied, consumed, and teardown-invalidated receipts return `false`; unknown fields
|
|
452
|
+
* still throw `FIELD`.
|
|
453
|
+
*
|
|
454
|
+
* @param name - The field's name.
|
|
455
|
+
* @param message - What to tell the person.
|
|
456
|
+
* @param submission - The active receipt to check against the field's captured revision.
|
|
457
|
+
* @returns True if the failure was applied; false if the receipt or field revision is stale.
|
|
458
|
+
*/
|
|
459
|
+
invalidate(name: string, message: string, submission?: FormSubmission): boolean;
|
|
460
|
+
/**
|
|
461
|
+
* Takes one field, several fields, or every field out of the form.
|
|
462
|
+
*
|
|
463
|
+
* @remarks
|
|
464
|
+
* A disabled field is neither evaluated nor submitted. Its answer is kept, and so is any
|
|
465
|
+
* {@link FormInterface.invalidate} failure it carries, which is withheld from `errors` while
|
|
466
|
+
* the field is out and restored when it comes back. The form emits `disable` once per field
|
|
467
|
+
* whose state actually moved, in schema order, so a call that moves nothing announces
|
|
468
|
+
* nothing. Disabling is a write, so a settled or abandoned form refuses it.
|
|
469
|
+
*/
|
|
470
|
+
disable(): void;
|
|
471
|
+
/**
|
|
472
|
+
* Takes one field out of the form.
|
|
473
|
+
*
|
|
474
|
+
* @param name - The field's name.
|
|
475
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name.
|
|
476
|
+
*/
|
|
477
|
+
disable(name: string): void;
|
|
478
|
+
/**
|
|
479
|
+
* Takes several fields out of the form.
|
|
480
|
+
*
|
|
481
|
+
* @param names - The field names.
|
|
482
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name. Every name
|
|
483
|
+
* is checked before any field moves, so one bad name leaves the whole call undone.
|
|
484
|
+
*/
|
|
485
|
+
disable(names: readonly string[]): void;
|
|
486
|
+
/**
|
|
487
|
+
* Puts one field, several fields, or every field back into the form.
|
|
488
|
+
*
|
|
489
|
+
* @remarks
|
|
490
|
+
* An enabled field is evaluated and submitted again, and any invalidation held while it was
|
|
491
|
+
* out reappears in `errors`. The form emits `enable` once per field whose state actually
|
|
492
|
+
* moved, in schema order. Enabling is a write, so a settled or abandoned form refuses it.
|
|
493
|
+
*/
|
|
494
|
+
enable(): void;
|
|
495
|
+
/**
|
|
496
|
+
* Puts one field back into the form.
|
|
497
|
+
*
|
|
498
|
+
* @param name - The field's name.
|
|
499
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name.
|
|
500
|
+
*/
|
|
501
|
+
enable(name: string): void;
|
|
502
|
+
/**
|
|
503
|
+
* Puts several fields back into the form.
|
|
504
|
+
*
|
|
505
|
+
* @param names - The field names.
|
|
506
|
+
* @throws A {@link FormError} coded `FIELD` when the schema declares no such name. Every name
|
|
507
|
+
* is checked before any field moves, so one bad name leaves the whole call undone.
|
|
508
|
+
*/
|
|
509
|
+
enable(names: readonly string[]): void;
|
|
510
|
+
/**
|
|
511
|
+
* Checks every answer and settles the form when they all pass.
|
|
512
|
+
*
|
|
513
|
+
* @returns The values on success, or every error that stopped them.
|
|
514
|
+
*/
|
|
515
|
+
submit(): FormResult;
|
|
516
|
+
/**
|
|
517
|
+
* Prepares an owned enabled snapshot for acknowledgement while leaving the form editable.
|
|
518
|
+
*
|
|
519
|
+
* @returns The submission on success, or the errors that prevented preparation.
|
|
520
|
+
* @throws A {@link FormError} coded `PENDING` for an overlapping preparation or pending receipt,
|
|
521
|
+
* or when clear interrupts preparation; terminal forms retain their terminal refusals.
|
|
522
|
+
*/
|
|
523
|
+
prepare(): FormPreparationResult;
|
|
524
|
+
/**
|
|
525
|
+
* Adopts the active submission's captured answers into the baseline without changing live state.
|
|
526
|
+
*
|
|
527
|
+
* @param submission - The receipt returned by preparation.
|
|
528
|
+
* @returns True if this active receipt was accepted; false otherwise, including after teardown.
|
|
529
|
+
*/
|
|
530
|
+
accept(submission: FormSubmission): boolean;
|
|
531
|
+
/**
|
|
532
|
+
* Consumes the active submission and aborts its signal without changing answers or baseline.
|
|
533
|
+
*
|
|
534
|
+
* @param submission - The receipt returned by preparation.
|
|
535
|
+
* @returns True if this active receipt was aborted; false otherwise, including after teardown.
|
|
536
|
+
*/
|
|
537
|
+
abort(submission: FormSubmission): boolean;
|
|
538
|
+
/**
|
|
539
|
+
* Returns every answer to its acknowledged baseline and aborts pending acknowledgement.
|
|
540
|
+
*
|
|
541
|
+
* @remarks
|
|
542
|
+
* Those answers are {@link FormInterface.baseline}. The runtime disabled overlay resets with
|
|
543
|
+
* them, so {@link FormInterface.disabled} reads the schema's declarations again.
|
|
544
|
+
*/
|
|
545
|
+
clear(): void;
|
|
546
|
+
/**
|
|
547
|
+
* Tears the form down, abandoning it when it has not settled.
|
|
548
|
+
*
|
|
549
|
+
* @remarks
|
|
550
|
+
* Tearing down twice does nothing the second time. A request from inside a listener defers
|
|
551
|
+
* teardown until the outermost mutation batch closes, so an in-flight settlement can win and
|
|
552
|
+
* leave the form `settled` rather than `abandoned`.
|
|
553
|
+
*/
|
|
554
|
+
destroy(): void;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Reports a prepared submission, or every error that prevented its preparation. */
|
|
558
|
+
declare type FormPreparationResult = Result<FormSubmission, readonly FieldError[]>;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Reports what a submit answers with: the values, or every error that stopped them.
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```ts
|
|
565
|
+
* const result: FormResult = form.submit()
|
|
566
|
+
* const email = result.success ? result.value.email : undefined
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
declare type FormResult = Result<FormValues, readonly FieldError[]>;
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Describes everything a form asks.
|
|
573
|
+
*
|
|
574
|
+
* @remarks
|
|
575
|
+
* `fields` is the schema's only required member, and the order it declares is the order the
|
|
576
|
+
* form presents. `name`, `label`, and `help` describe the form itself.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```ts
|
|
580
|
+
* const schema: FormSchema = {
|
|
581
|
+
* label: 'Sign up',
|
|
582
|
+
* fields: [
|
|
583
|
+
* { control: 'text', name: 'email', label: 'Email', rule: { required: true, email: true } },
|
|
584
|
+
* { control: 'confirm', name: 'terms', label: 'I accept the terms' },
|
|
585
|
+
* ],
|
|
586
|
+
* }
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
589
|
+
declare interface FormSchema {
|
|
590
|
+
readonly name?: string;
|
|
591
|
+
readonly label?: string;
|
|
592
|
+
readonly help?: string;
|
|
593
|
+
readonly groups?: readonly FormGroup[];
|
|
594
|
+
readonly fields: readonly FormField[];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Represents where a form sits in its life.
|
|
599
|
+
*
|
|
600
|
+
* @remarks
|
|
601
|
+
* A form opens `editing`, turns `settled` on its first valid submit, and turns `abandoned`
|
|
602
|
+
* when it is destroyed before settling. Both end states are terminal, and a write to a form
|
|
603
|
+
* in either one is refused. A destroy requested while a mutation batch is open is recorded,
|
|
604
|
+
* refuses every subsequent write immediately, and defers teardown until the outermost batch
|
|
605
|
+
* closes. The batch outcome wins: a batch that settles the form leaves it `settled`, resolves
|
|
606
|
+
* `answer`, and emits no `abandon`. Teardown never advances into, aborts, or rolls back the
|
|
607
|
+
* batch. The pending request is private, unnamed state and adds no fourth status.
|
|
608
|
+
*/
|
|
609
|
+
declare type FormStatus = 'editing' | 'settled' | 'abandoned';
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Carries an owned, frozen answer snapshot and the signal for its pending acknowledgement.
|
|
613
|
+
*
|
|
614
|
+
* @remarks
|
|
615
|
+
* Only the producing form accepts this object's identity. Copying its members cannot create a
|
|
616
|
+
* receipt. The values and their lists are frozen; the native signal remains usable by transports.
|
|
617
|
+
*/
|
|
618
|
+
declare interface FormSubmission {
|
|
619
|
+
readonly values: FormValues;
|
|
620
|
+
readonly signal: AbortSignal;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Represents a form's answers, keyed by field name.
|
|
625
|
+
*
|
|
626
|
+
* @remarks
|
|
627
|
+
* A name with no key is a field nobody has answered. A disabled field's value may appear so a
|
|
628
|
+
* renderer can show it, but the value is never evaluated or submitted.
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* ```ts
|
|
632
|
+
* const values: FormValues = { email: 'ada@example.com', terms: true }
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
declare type FormValues = Readonly<Record<string, FieldValue>>;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Checks whether a value is a native binding refusal.
|
|
639
|
+
* @param value - The value to inspect.
|
|
640
|
+
* @returns True if the value is a native binding refusal; false otherwise.
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* isFormBindingError(new FormBindingError('CONTROL', 'Duplicate text controls')) // true
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
export declare function isFormBindingError(value: unknown): value is FormBindingError;
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Checks whether a value is a native answer control.
|
|
650
|
+
* @param value - The value to inspect.
|
|
651
|
+
* @returns True if the value is an input, select, or textarea; false otherwise.
|
|
652
|
+
* @example
|
|
653
|
+
* ```ts
|
|
654
|
+
* isFormControl(document.createElement('input')) // true
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
export declare function isFormControl(value: unknown): value is FormControl;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Checks a native control population against a field's declared value shape.
|
|
661
|
+
* @param field - The declared field.
|
|
662
|
+
* @param controls - Associated controls sharing its name.
|
|
663
|
+
* @returns True if the population has an unambiguous compatible shape; false otherwise.
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* matchesFormControls({ control: 'text', name: 'name' }, [document.createElement('input')]) // true
|
|
667
|
+
* ```
|
|
668
|
+
*/
|
|
669
|
+
export declare function matchesFormControls(field: FormField, controls: readonly FormControl[]): boolean;
|
|
670
|
+
|
|
671
|
+
/** Represents a number. */
|
|
672
|
+
declare interface NumberField extends FieldBase {
|
|
673
|
+
readonly control: 'number';
|
|
674
|
+
readonly default?: number;
|
|
675
|
+
readonly placeholder?: string;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Represents a secret, obscured as it is typed.
|
|
680
|
+
*
|
|
681
|
+
* @remarks
|
|
682
|
+
* It carries no `default` deliberately: a seeded secret is a secret written down. `mask` is
|
|
683
|
+
* the character the control repeats in place of the text.
|
|
684
|
+
*/
|
|
685
|
+
declare interface PasswordField extends FieldBase {
|
|
686
|
+
readonly control: 'password';
|
|
687
|
+
readonly mask?: string;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Reads one compatible native population using the document's blank-to-absence semantics.
|
|
692
|
+
* @param field - The field declaring the answer shape.
|
|
693
|
+
* @param controls - Controls sharing the field's name, in native order.
|
|
694
|
+
* @returns The native answer, or undefined for an absent answer or empty population.
|
|
695
|
+
* @throws Thrown when the population or answer is incompatible, including numeric bad input.
|
|
696
|
+
* @example
|
|
697
|
+
* ```ts
|
|
698
|
+
* const input = document.createElement('input')
|
|
699
|
+
* input.value = 'Ada'
|
|
700
|
+
* readFormControls({ control: 'text', name: 'name' }, [input]) // 'Ada'
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
export declare function readFormControls(field: FormField, controls: readonly FormControl[]): FieldValue | undefined;
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Reads native constraint messages not already reported by document rules.
|
|
707
|
+
* @param control - The native control with binding-owned custom validity removed.
|
|
708
|
+
* @param errors - The document's reported failures.
|
|
709
|
+
* @returns Uncovered constraint messages, preserving authored custom validity.
|
|
710
|
+
* @remarks
|
|
711
|
+
* A lone native failure retains platform copy. Competing failures use constraint-specific
|
|
712
|
+
* copy because the platform exposes only one message for the whole control.
|
|
713
|
+
* @example
|
|
714
|
+
* ```ts
|
|
715
|
+
* const input = document.createElement('input')
|
|
716
|
+
* input.name = 'name'
|
|
717
|
+
* input.required = true
|
|
718
|
+
* readNativeMessages(input, [{ field: 'name', rule: 'required', message: 'Enter a name' }]) // []
|
|
719
|
+
* ```
|
|
720
|
+
*/
|
|
721
|
+
export declare function readNativeMessages(control: FormControl, errors: readonly FieldError[]): readonly string[];
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Represents one choice out of a list.
|
|
725
|
+
*
|
|
726
|
+
* @remarks
|
|
727
|
+
* `open` admits a value the list does not offer, which is what turns a closed menu into a
|
|
728
|
+
* suggestion list.
|
|
729
|
+
*/
|
|
730
|
+
declare interface SelectField extends FieldBase {
|
|
731
|
+
readonly control: 'select';
|
|
732
|
+
readonly choices: readonly FieldChoice[];
|
|
733
|
+
readonly default?: string;
|
|
734
|
+
readonly open?: boolean;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** Represents a single line of text. */
|
|
738
|
+
declare interface TextField extends FieldBase {
|
|
739
|
+
readonly control: 'text';
|
|
740
|
+
readonly default?: string;
|
|
741
|
+
readonly placeholder?: string;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** Represents a time of day, held as the control's own `HH:MM` string, with seconds optional. */
|
|
745
|
+
declare interface TimeField extends FieldBase {
|
|
746
|
+
readonly control: 'time';
|
|
747
|
+
readonly default?: string;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export { }
|