@dynamicforms/vue-forms 0.6.1 → 0.12.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/index.d.ts CHANGED
@@ -7,27 +7,102 @@ import { Ref } from 'vue';
7
7
  export declare class AbortEventHandlingException extends Error {
8
8
  }
9
9
 
10
- export declare class Action<T extends ActionValue = ActionValue> extends Field<T> {
11
- protected init(params?: Partial<IFieldConstructorParams<T>>): void;
10
+ export declare class Action<T extends ActionValue = ActionValue, X extends object = {}> extends Field<T, X> {
11
+ protected init(params?: IFieldParams<T, X>): void;
12
+ /**
13
+ * A copy of the value this action holds, carrying one member rewritten. A member handed undefined is dropped
14
+ * from the copy rather than written into it: an own key holding undefined is invisible to a reader and to
15
+ * JSON, but lodash isEqual compares own-key sets, so writing one would leave the action permanently changed
16
+ * against a baseline that never carried the key.
17
+ */
18
+ private valueWith;
12
19
  get icon(): string | undefined;
13
20
  set icon(newValue: string | undefined);
14
21
  get label(): string | undefined;
22
+ /**
23
+ * The two setters hand the value setter a new object rather than writing into the one the action holds, so each
24
+ * is an ordinary value change: the handlers fire, isChanged answers over it, and a disabled action refuses it.
25
+ * The new object is built only for a member that differs from the one held - the value setter compares by
26
+ * identity and every copy is a new object, so without the comparison here a write of the value already held
27
+ * would announce a change and invalidate the value cache of every container above.
28
+ */
15
29
  set label(newValue: string | undefined);
16
- execute(params: any): void;
30
+ /** the counter behind `busy`, allocated for this action the first time it is asked for */
31
+ private get busyRuns();
32
+ /** true from the moment execute() is called until the handler it ran has settled, however it settles */
33
+ get busy(): boolean;
34
+ /**
35
+ * Runs the ExecuteAction chain registered on this action and answers what the chain returned. `busy` stands for
36
+ * the length of the run: the chain is entered synchronously, and the flag is cleared once the value it produced
37
+ * has settled - a handler that returns a promise holds it for as long as that promise takes, and one that
38
+ * rejects clears it as it rejects.
39
+ *
40
+ * The answer is a promise whatever the handler returns, so a handler that throws rejects it rather than throwing
41
+ * out of this call: a caller that does not await the answer and does not attach a catch handler leaves that
42
+ * rejection unhandled.
43
+ */
44
+ execute(params?: any): Promise<any>;
17
45
  }
18
46
 
19
47
  declare type ActionExecutor = (field: FieldBase, supr: FieldActionExecute, ...params: any[]) => any;
20
48
 
21
- export declare class ActionsMap extends Map<symbol, FieldActionExecute> {
22
- private readonly eagerActions;
49
+ /**
50
+ * The actions one element has registered, grouped by the identifier they are triggered under. Within a group the
51
+ * actions stand in registration order and are run from the end backwards, so the newest registration is the
52
+ * outermost handler and reaches the ones before it through the `supr` it is handed. A handler that does not call
53
+ * `supr` ends the run there, and one that calls it may transform what it answers with.
54
+ */
55
+ export declare class ActionsMap {
56
+ /** every action registered here, in registration order across all identifiers */
23
57
  private readonly registeredActions;
24
- register(action: FieldActionBase): void;
58
+ /** the actions of one identifier, in registration order */
59
+ private readonly chains;
60
+ /** the eager actions of one identifier, in registration order; a subsequence of the chain under that identifier */
61
+ private readonly eagerChains;
62
+ /**
63
+ * Registers `action`. It becomes the outermost handler of its identifier, or - where `before` is given and is
64
+ * registered here under the same identifier - takes that action's place in the order, so `before` wraps it and
65
+ * reaches it through `supr`. That is what lets an action be added to a chain someone else built and still sit
66
+ * inside a handler already there.
67
+ */
68
+ register(action: FieldActionBase, before?: FieldActionBase): void;
69
+ /**
70
+ * Drops `action` from this map and answers whether it was in it. The lists it stood in are replaced rather than
71
+ * written, so a run already walking one finishes on the list it started with and the removal takes effect from
72
+ * the next trigger.
73
+ */
74
+ unregister(action: FieldActionBase): boolean;
75
+ /** True where something is registered under `identifier`. */
76
+ willTrigger(identifier: symbol): boolean;
77
+ /** True where any eager action is registered, whatever identifier it stands under. */
78
+ get hasEager(): boolean;
79
+ /** Runs the actions registered under `ActionClass` and answers with what the outermost of them returned. */
25
80
  trigger<T extends FieldActionBase>(ActionClass: (abstract new (...args: any[]) => T) & {
26
81
  classIdentifier: symbol;
27
82
  }, field: FieldBase, ...params: any[]): any;
28
- triggerEager(field: FieldBase, ...params: any[]): any;
83
+ /** Runs the eager actions of every identifier, each group on its own. */
84
+ triggerEager(field: FieldBase, ...params: any[]): void;
85
+ /** Runs the eager actions registered under `identifier` and nothing else. */
86
+ triggerEagerFor(identifier: symbol, field: FieldBase, ...params: any[]): any;
87
+ /** the validators registered in this map, in registration order */
88
+ get validators(): Validator[];
29
89
  clone(): ActionsMap;
30
- cloneWithoutValidators(): ActionsMap;
90
+ /**
91
+ * Tells every action in this map that it serves `owner`. A copied map holds the instances the original holds, so
92
+ * the element that took the copy on is announced to them here, the way registering an action announces the
93
+ * element it was registered on.
94
+ */
95
+ bindTo(owner: FieldBase): void;
96
+ /**
97
+ * Walks `chain` from the end backwards, handing each action a `supr` that continues at the one before it. The
98
+ * closures exist for the length of the run and only as deep as the run actually goes, so a chain nobody walks to
99
+ * the bottom costs nothing for the part left unreached.
100
+ */
101
+ private step;
102
+ /** Walks a chain, answering null where there is none. An abort ends the run and is not an error to the caller. */
103
+ private run;
104
+ private insert;
105
+ private remove;
31
106
  }
32
107
 
33
108
  export declare interface ActionValue {
@@ -47,13 +122,55 @@ export declare type ClassType = string | string[] | Record<string, boolean>;
47
122
  export declare type ClassTypes = ClassType | ClassType[];
48
123
 
49
124
  declare class CompareTo<T = any> extends Validator {
50
- private otherField;
51
125
  private isValidComparison;
52
- private unregistered;
53
- constructor(otherField: FieldBase, isValidComparison: (myValue: T, otherValue: T) => boolean, message: RenderContentRef);
54
- unregister(): void;
126
+ private readonly otherField;
127
+ /**
128
+ * The fields this validator is registered on. Registration reaches one element at a time - the rows of a list
129
+ * take the validator on one by one, as they are built from the item template that carries it - so this is what
130
+ * says which of the fields a declaration stands for actually carry the rule, and a field that never took it on
131
+ * is left alone by a change of the compared field.
132
+ */
133
+ private readonly registrations;
134
+ /**
135
+ * The fields this validator has installed its listener on. A binding of such a field carries the listener too -
136
+ * a binding takes on the actions of the field it was bound from - so the field it was bound from is what is
137
+ * asked about as well, and a list of a thousand rows installs one listener rather than a thousand.
138
+ */
139
+ private readonly listening;
140
+ /**
141
+ * What the fields this validator was registered on were declared as. One entry stands for every binding of such a
142
+ * field, so a change of a compared field finds the fields of the record that change happened in without a list
143
+ * of a thousand rows being held here.
144
+ */
145
+ private readonly declarations;
146
+ constructor(otherField: CompareToTarget, isValidComparison: (myValue: T, otherValue: T) => boolean, message: RenderContentRef);
147
+ /** The field `field` is compared against, within the record `field` belongs to. */
148
+ private resolve;
149
+ /**
150
+ * Makes a change of `other` re-run this comparison. The listener re-runs it over the fields of the record the
151
+ * change happened in rather than over the field that installed it, so the listener a row inherits with the field
152
+ * it was bound from serves that row; and it re-runs this one validator, not the chain around it.
153
+ */
154
+ private listenOn;
155
+ boundToBinding(binding: FieldBase): void;
156
+ private comparisonState;
157
+ protected newBindingState(): CompareToBindingState<T>;
158
+ unregisterFrom(binding: FieldBase): void;
55
159
  }
56
160
 
161
+ /** What the validator remembers about one field it validates. */
162
+ declare interface CompareToBindingState<T> extends ValidatorBindingState {
163
+ oldValue: T;
164
+ }
165
+
166
+ /**
167
+ * How the field to compare against is named. A field names it directly, a string names it by the name its
168
+ * container holds it under, and a callback works it out from the field being validated. All three answer for the
169
+ * record the validation is running over: handed the field of a `List`'s item template, a row compares against that
170
+ * row's field.
171
+ */
172
+ declare type CompareToTarget = FieldBase | string | ((field: FieldBase) => FieldBase | null | undefined);
173
+
57
174
  export declare class ConditionalEnabledAction extends ConditionalStatementAction {
58
175
  constructor(statement: Statement);
59
176
  }
@@ -61,12 +178,44 @@ export declare class ConditionalEnabledAction extends ConditionalStatementAction
61
178
  declare type ConditionalExecutorFn = (field: FieldBase, currentResult: boolean, previousResult: boolean | undefined) => void;
62
179
 
63
180
  export declare class ConditionalStatementAction extends ValueChangedAction {
64
- private lastResult;
65
- private readonly boundFields;
181
+ private readonly statement;
182
+ private readonly conditionalExecutor;
183
+ /**
184
+ * What the elements this action was registered on were declared as. It holds declarations rather than the
185
+ * elements themselves, so one entry stands for every row of a list, and the element a result is applied to is
186
+ * the one that declaration names within the record the change happened in.
187
+ */
188
+ private readonly declarations;
189
+ /**
190
+ * The elements this action was registered on. Registration reaches one element at a time - the rows of a list
191
+ * take the action on one by one, as they are built from the item template that carries it - so this is what
192
+ * says which of the elements a declaration stands for the action actually drives.
193
+ */
194
+ private readonly registrations;
66
195
  constructor(statement: Statement, executorFn: ConditionalExecutorFn);
67
196
  static get classIdentifier(): symbol;
68
197
  get eager(): boolean;
69
- boundToField(field: FieldBase): void;
198
+ boundToBinding(binding: FieldBase): void;
199
+ unregisterFrom(binding: FieldBase): void;
200
+ /**
201
+ * The elements of `scope` this action controls: what each declaration it serves names within that record, and of
202
+ * those the ones that took the action on. A row of a list that never took it on is not one this action drives.
203
+ */
204
+ private targetsIn;
205
+ /**
206
+ * Re-evaluates the statement over one record and applies the result where it changed. The result is recorded
207
+ * before the executor runs, because an executor that writes a value re-enters through that value's eager pass
208
+ * and what it has to find there is the result being applied. It answers whether the record held anything for
209
+ * this action to drive, which is how a caller learns that the record is not assembled yet.
210
+ */
211
+ private applyIn;
212
+ /**
213
+ * Re-evaluates the records a change of `source` speaks for. A field of the record the targets live in speaks for
214
+ * that record alone, which is what keeps one row of a list from answering for another; a field above them - a
215
+ * form field every row reads - speaks for every record below it, and for the record the action was declared in
216
+ * where the change reaches none.
217
+ */
218
+ private applyFrom;
70
219
  }
71
220
 
72
221
  export declare class ConditionalValueAction<T> extends ConditionalStatementAction {
@@ -77,6 +226,15 @@ export declare class ConditionalVisibilityAction extends ConditionalStatementAct
77
226
  constructor(statement: Statement);
78
227
  }
79
228
 
229
+ /**
230
+ * What a container holds beyond the common slots: the object the value getter last built, together with the
231
+ * version of the tree it was built from.
232
+ */
233
+ declare interface ContainerSlots<T = any> extends ElementSlots<T> {
234
+ cachedValue: T;
235
+ cachedValueVersion: number;
236
+ }
237
+
80
238
  /**
81
239
  * DisplayMode enum provides an enumeration for supported ways of rendering a particular object in the DOM
82
240
  */
@@ -93,7 +251,64 @@ export declare namespace DisplayMode {
93
251
  export function isDefined(mode: number | string): boolean;
94
252
  }
95
253
 
96
- export declare const EmptyField: Field<string>;
254
+ /**
255
+ * The mutable state of one element, held in an object of its own beside the element rather than in the element's
256
+ * own properties.
257
+ *
258
+ * An element reaches it through two views of that one object, both in private class fields of `FieldBase`:
259
+ * `this.state` is `reactive(slots)` and `this.raw` is the object itself. A slot read through `state` inside a
260
+ * render effect or a computed subscribes that effect to the slot, and a write to the slot re-runs it. The
261
+ * bookkeeping an element does for itself goes through `raw`, which neither records a read nor announces a write.
262
+ * The value cache of a container goes through `raw` in particular: its getter writes the cache while it runs, and
263
+ * a tracked write there costs the reading effect one extra evaluation, and one extra render on mount, for a value
264
+ * it has already built.
265
+ *
266
+ * Being private class fields is what puts the two views out of reach of everything outside `FieldBase`: a private
267
+ * field is absent from `Object.keys`, `JSON.stringify`, `Object.getOwnPropertySymbols` and lodash `getAllKeys` by
268
+ * construction, and needs no per-instance property definition to be. Both `JSON.stringify` and lodash `isEqual`
269
+ * walk their way down a structure over own keys, enumerable symbols included, and the `parent` slot is in the
270
+ * state: reachable, it would take either walker back into the container the element came from.
271
+ *
272
+ * What they do reach on an element is `_actions` once something is registered, `_fields` on a `Group` and
273
+ * `_itemTemplate` on a `List`. All three lead downwards only.
274
+ */
275
+ declare interface ElementSlots<T = any> {
276
+ /** the value the element was given at construction; isChanged compares against it */
277
+ originalValue: T;
278
+ /** the value the last ValueChangedAction reported; the next transaction measures its net change against it */
279
+ announcedValue: T;
280
+ errors: ValidationError[];
281
+ visibility: DisplayMode;
282
+ enabled: boolean;
283
+ /** counts the writes that changed the value of the element or of anything below it */
284
+ valueVersion: number;
285
+ /** how many asynchronous validation runs are in flight on this element */
286
+ validatingCount: number;
287
+ /** the container that holds this element, absent while none does; takeChild writes it, releaseChild clears it */
288
+ parent: FieldBase | undefined;
289
+ /** the name the containing Group holds this element under; a List row carries none */
290
+ fieldName: string | undefined;
291
+ /** generation of the validators attached to the element; clearValidators() raises it */
292
+ validationEpoch: number;
293
+ /**
294
+ * The extended properties the element carries, typed by the element's own X parameter. A write replaces the
295
+ * object rather than writing into it, so an effect that read the slot re-runs on the write, and the object a
296
+ * transaction captured is the one a rollback puts back.
297
+ */
298
+ extra: object;
299
+ /**
300
+ * The element this one was bound from, absent on an element that was declared rather than bound. It is what lets
301
+ * an action shared by every binding work out which binding of a second element it means: `declaration` is the
302
+ * canonical one, so a binding of a binding names the element the whole family was declared as.
303
+ */
304
+ declaration: FieldBase | undefined;
305
+ /** the verdict the last commit announced, which is what a change of validity is measured against */
306
+ valid: boolean;
307
+ /** number of direct children whose last announced verdict was invalid */
308
+ invalidChildren: number;
309
+ }
310
+
311
+ export declare const EmptyField: Field<string, {}>;
97
312
 
98
313
  export declare class EnabledChangedAction extends FieldActionBase {
99
314
  constructor(executorFn: (field: FieldBase, supr: FieldActionExecute, newValue: boolean, oldValue: boolean) => void);
@@ -113,10 +328,13 @@ export declare class ExecuteAction extends FieldActionBase {
113
328
  execute(field: FieldBase, supr: FieldActionExecute, params: any): any;
114
329
  }
115
330
 
116
- export declare class Field<T = any> extends FieldBase<T> {
117
- protected _value: T;
118
- protected _touched: boolean;
119
- constructor(params?: Partial<IFieldConstructorParams<T>>);
331
+ export declare class Field<T = any, X extends object = {}> extends FieldBase<T, X> {
332
+ protected get state(): FieldSlots<T>;
333
+ protected get raw(): FieldSlots<T>;
334
+ /** the value slot itself, without the notifications the value setter carries around a write */
335
+ protected get _value(): T;
336
+ protected set _value(newValue: T);
337
+ constructor(params?: IFieldParams<T, X>);
120
338
  /**
121
339
  * Applies the constructor parameters. It is a hook so that a subclass needing different parameter handling
122
340
  * overrides one method instead of redeclaring the constructor - Action does exactly that.
@@ -126,81 +344,343 @@ export declare class Field<T = any> extends FieldBase<T> {
126
344
  * subclass initializes read as undefined inside it, and anything it writes to such a member is overwritten
127
345
  * the moment the initializer runs.
128
346
  */
129
- protected init(params?: Partial<IFieldConstructorParams<T>>): void;
347
+ protected init(params?: IFieldParams<T, X>): void;
130
348
  get value(): T;
131
349
  set value(newValue: T);
132
350
  get touched(): boolean;
133
351
  set touched(touched: boolean);
134
- clone(overrides?: Partial<IFieldConstructorParams<T>>): this;
352
+ bind(data?: T, overrides?: IBindParams<T, X>): this;
135
353
  }
136
354
 
137
355
  export declare abstract class FieldActionBase {
138
356
  static get classIdentifier(): symbol;
139
357
  get classIdentifier(): symbol;
140
358
  private readonly executorFn;
359
+ /**
360
+ * What this action remembers about one of the elements it serves. An action instance is shared - a `List` row
361
+ * carries the instances the item template carries - so anything an action holds about the element it last ran
362
+ * over belongs to that element rather than to the action: a verdict, a sequence number, a listener it installed.
363
+ * The keys are weak, so what an action remembers about a row is released with the row.
364
+ */
365
+ private readonly states;
141
366
  constructor(executorFn: ActionExecutor);
367
+ /**
368
+ * This action's state for `key`, created by `init` the first time it is asked for. The key is the element the
369
+ * action is running over, or the record that element belongs to where what is remembered is a fact about the
370
+ * whole record.
371
+ */
372
+ protected state<S>(key: object, init: () => S): S;
142
373
  execute(field: FieldBase, supr: FieldActionExecute, ...params: any[]): any;
143
374
  get eager(): boolean;
144
- boundToField(field: FieldBase): void;
145
- unregister(): void;
375
+ /**
376
+ * Announces that this action now serves `binding`. It runs once per element the action is registered on, the
377
+ * copies of that element included: a copy takes on the instances it was copied from and each is told about the
378
+ * copy as it does.
379
+ */
380
+ boundToBinding(binding: FieldBase): void;
381
+ /**
382
+ * Announces that `binding` no longer holds this action, and takes back what running it left there. It names one
383
+ * element rather than the action as a whole, because the instance goes on serving every other element it was
384
+ * registered on. It runs inside the operation that drops the registration, so an operation that unwinds puts the
385
+ * registration and what this took back both back.
386
+ */
387
+ unregisterFrom(binding: FieldBase): void;
146
388
  }
147
389
 
148
390
  export declare type FieldActionExecute<T = any> = (field: FieldBase<T>, ...params: any[]) => any;
149
391
 
150
- export declare abstract class FieldBase<T = any> {
392
+ export declare abstract class FieldBase<T = any, X extends object = {}> {
393
+ #private;
151
394
  /**
152
- * A field is a Vue reactive proxy from construction onwards: the base constructor returns the proxy, and a
153
- * derived class's `this` is whatever super() returned, so every subclass constructor, field initializer and
154
- * method sees the proxy. Consequence: plain `field.property = value` is tracked, with no wrapper in between.
395
+ * Vue's getTargetType answers INVALID for an object that carries __v_skip, so reactive() hands an element back
396
+ * unwrapped and no element is ever behind a proxy of its own. isReactive() reads the second flag, and it stays
397
+ * true because what carries an element's reactivity - its state - is a reactive object.
398
+ *
399
+ * Two consequences for a consumer, both silent. watch(field, cb) with a bare element as the source registers
400
+ * no dependency, because the deep traversal Vue starts for a reactive source stops on __v_skip; the supported
401
+ * form is watch(() => field.value, cb). And readonly(field) hands the element straight back, so the value it
402
+ * returns is the element itself and a write through it reaches the field; what a caller hands out instead is
403
+ * the value, which is frozen, or a computed over it.
155
404
  */
156
- constructor();
405
+ get __v_skip(): boolean;
406
+ get __v_isReactive(): boolean;
407
+ constructor(slots: ElementSlots<T>);
408
+ /** the tracked view of this element's state; a subclass narrows the return type to its own slots */
409
+ protected get state(): ElementSlots<T>;
410
+ /** the untracked view of this element's state; a subclass narrows the return type to its own slots */
411
+ protected get raw(): ElementSlots<T>;
157
412
  abstract get value(): T;
158
413
  abstract set value(newValue: T);
159
414
  abstract get touched(): boolean;
160
415
  abstract set touched(touched: boolean);
161
- abstract clone(overrides?: Partial<IFieldConstructorParams<T>>): FieldBase<T>;
162
- originalValue: T;
163
- private validatingCount;
416
+ /**
417
+ * A new element of this one's class over `data`: same class, same registered actions, same extended properties,
418
+ * and the change history starting over. It is how a declaration is put to work over a record - every row a
419
+ * `List` builds is its item template bound to that row's data - and the element it is called on is what the new
420
+ * one answers `declaration` with.
421
+ *
422
+ * `data` of `undefined` is no data supplied and the new element carries what this one holds; an explicit `null`
423
+ * is data and clears. `overrides` states the rest: `originalValue` is read by key presence, `enabled` and
424
+ * `visibility` fall back to this element's, and extended properties it names are written over the ones carried
425
+ * over. The new element is detached - no `parent`, no `fieldName` - so it is free to be taken by a container.
426
+ */
427
+ abstract bind(data?: T, overrides?: IBindParams<T, X>): FieldBase<T, X>;
428
+ /**
429
+ * Exchanges the data this element holds for `data`, in place. The element is the same instance afterwards - its
430
+ * identity, its actions, its extended properties and its place in whatever container holds it all stand - and it
431
+ * ends up in the state `bind(data)` would have produced: the values are written, the change history starts over
432
+ * and the validators run. It is what recycles one element across records, which is what a virtualised renderer
433
+ * does with the rows it keeps.
434
+ *
435
+ * The element makes no statement of its own about the exchange: no `ValueChangedAction` fires for it, the way
436
+ * none fires for an element that was just built. Its members do announce theirs, and a verdict that moves is
437
+ * announced as always - a rebound element that is invalid says so to the container holding it. A change an
438
+ * open transaction is already owed an announcement for stands: the commit reports the pair measured from
439
+ * where the element was when the transaction opened.
440
+ *
441
+ * The data is measured against the element's `declaration`, so a key a record leaves out is taken from the
442
+ * declaration rather than left as the previous record had it.
443
+ */
444
+ rebind(data: T): this;
445
+ /** contains original field value as was provided at creation */
446
+ get originalValue(): T;
447
+ set originalValue(newValue: T);
448
+ /**
449
+ * The extended properties this element carries: what a consumer attached to it beyond the members the class
450
+ * declares, such as the label, hint or css class a UI layer binds to the input it renders the element with.
451
+ * The read is tracked, so a template rendering off `field.extra.label` re-renders when the property is written.
452
+ *
453
+ * Every property is optional here, whatever `X` states: a parameter object need not carry one and
454
+ * setExtendedValues writes as few as a caller likes, so a property `X` declares as required is present only
455
+ * once something has written it.
456
+ *
457
+ * The object is frozen and setExtendedValues is the way to change it: it is the object a transaction captured,
458
+ * and one written in place would leave a rollback with nothing to put back.
459
+ */
460
+ get extra(): Readonly<Partial<X>>;
461
+ /**
462
+ * Writes extended properties. The values given are merged over the ones the element carries, so a write of one
463
+ * property leaves the others standing, and the merged set replaces the object the element held.
464
+ */
465
+ setExtendedValues(values: Partial<X>): void;
466
+ /**
467
+ * The extended properties a parameter object carries: every key the element does not answer for itself. A key
468
+ * the element answers for at the time the parameters are applied - `value`, `enabled`, an accessor a subclass
469
+ * adds - is the element's own; a key that only Object.prototype answers for is nobody's declaration and counts
470
+ * as extended, and `validators` and `actions` are neither, since they state what to register on the element.
471
+ *
472
+ * A member a subclass declares as a class field is not among the keys the element answers for: class fields are
473
+ * defined on the instance after the base constructor returns, and the parameters are applied inside it. A
474
+ * subclass that wants a parameter of its own name assigned to it declares that member as an accessor.
475
+ */
476
+ protected extendedOf(params: object): Partial<X>;
477
+ /**
478
+ * Applies a parameter object: the members the element answers for are assigned to the element and the rest
479
+ * become its extended properties. Assigning a getter-only member throws a TypeError, which is what a caller
480
+ * naming `valid` or `parent` past the type system gets.
481
+ */
482
+ protected assignParams(params: object): void;
483
+ /**
484
+ * Takes this element into the record of the open transaction, where one is open. A write of a single slot that
485
+ * announces nothing calls it instead of opening a transaction of its own: on its own such a write is already
486
+ * atomic, and inside a transaction it has to be part of what a rollback puts back.
487
+ */
488
+ protected touchState(): void;
489
+ /**
490
+ * Records the whole of this element's mutable state, so that a rolled-back transaction can put it back exactly
491
+ * as it found it. The errors array is copied because a validator writes into the array it is handed rather than
492
+ * replacing it, and a subclass copies whatever else it holds by reference the same way.
493
+ *
494
+ * What is not in the snapshot is the action map, which no write touches: an operation that replaces it hands
495
+ * the transaction its own undo instead, so this stays the one shape every element captures.
496
+ */
497
+ protected [TxCapture](): TxSnapshot;
498
+ /**
499
+ * Puts a captured state back. It writes through the tracked view, so an effect that ran on the state the
500
+ * transaction produced runs again on the state it is being returned to, and only for the slots that differ.
501
+ *
502
+ * validatingCount is the one slot the element keeps: it counts runs that are in flight, and a rollback cannot
503
+ * un-start one. Put back, the count would no longer match the endValidating calls still to come.
504
+ */
505
+ protected [TxRestore](snapshot: TxSnapshot): void;
164
506
  /** true while at least one asynchronous validator is still running for this field */
165
507
  get validating(): boolean;
166
508
  /** announces the start of one asynchronous validation run; validators pair it with endValidating */
167
509
  beginValidating(): void;
168
510
  /** announces the end of one asynchronous validation run */
169
511
  endValidating(): void;
170
- protected _valid: boolean;
171
- errors: ValidationError[];
172
- parent?: Group;
173
- fieldName?: string;
174
- protected actions: ActionsMap;
175
- private _visibility;
512
+ /**
513
+ * List of errors. The array handed out is the one the element holds, and a validator writes into it in place,
514
+ * so a transaction that is open takes the element into its record here: a rollback that put back everything
515
+ * except the errors would leave a state the form never held.
516
+ */
517
+ get errors(): ValidationError[];
518
+ set errors(newValue: ValidationError[]);
519
+ /**
520
+ * The container that holds this element: the Group it is a member of, or the List whose row it is, and
521
+ * undefined while no container holds it. The container writes it; everyone else reads it. The read is tracked,
522
+ * so an effect rendering off the link - `v-if="field.parent"` - re-runs when a container takes the element or
523
+ * releases it.
524
+ */
525
+ get parent(): Group | undefined;
526
+ /** when member of a Group, fieldName specifies the name of this field. Written by the group, read-only after */
527
+ get fieldName(): string | undefined;
528
+ /**
529
+ * The element this one was declared as: itself for an element built from parameters, and the element it was
530
+ * bound from for a binding - transitively, so a binding of a binding names the same one. Every row a `List`
531
+ * builds from an item template is a binding of it, so a row's member and the template's member it stands for
532
+ * answer with the same element, and an action registered on the template can tell the two apart.
533
+ */
534
+ get declaration(): FieldBase;
535
+ /** The elements this one holds: a Group's members, a List's rows, none for a leaf. */
536
+ protected get members(): FieldBase[];
537
+ /**
538
+ * Every element in this element's subtree, this element included, that was declared as `declaration`. It answers
539
+ * the question a shared action asks when the record it changed in is not the record its targets live in - a form
540
+ * field that every row of a list reacts to - and it costs the subtree, so it is the path taken only when the
541
+ * cheaper one, resolving within a single record, does not apply.
542
+ */
543
+ bindingsOf(declaration: FieldBase): FieldBase[];
544
+ /** set while an eager pass over this element is waiting for the record the element belongs to */
545
+ protected _recordPending?: boolean;
546
+ /**
547
+ * States that an eager pass over this element reached an element it needs and did not find it, because the
548
+ * record this element belongs to was not assembled at the time: a `List` row is built member by member and its
549
+ * members are bound before any of them holds the row, so a validator comparing two of them runs before the
550
+ * row exists. The container that finishes the record runs the pass again, and a pass that still reaches
551
+ * nothing says so again, so the next container above answers for it.
552
+ */
553
+ markRecordIncomplete(): void;
554
+ /**
555
+ * Runs the eager passes outstanding in `element`'s subtree, now that the record it belongs to is assembled. A
556
+ * container calls it where it has just made a record complete: a Group that has written its members, and a
557
+ * List that has taken a row, which is what gives the row's fields the form above it.
558
+ */
559
+ protected completeRecords(element?: FieldBase): void;
560
+ /** Runs this element's eager actions over the value it holds and re-forms the verdict they reach. */
561
+ private rerunEagerActions;
562
+ /**
563
+ * The map this element registers its actions in, absent while it has registered none. Code that only triggers
564
+ * reads this slot rather than `actions`, so an element with nothing registered stays free of a map: `actions`
565
+ * allocates one for whoever asks, and a trigger over an empty map does nothing anyway.
566
+ */
567
+ protected _actions?: ActionsMap;
568
+ protected get actions(): ActionsMap;
569
+ protected set actions(newValue: ActionsMap);
570
+ /**
571
+ * Counts the writes that have changed the value of this element or of anything below it. A container reads it
572
+ * before answering from its value cache, and the read is an ordinary tracked one, so an effect that took a
573
+ * cached value still re-runs when a descendant is written.
574
+ */
575
+ protected get valueVersion(): number;
576
+ /**
577
+ * Marks the value of this element and of every ancestor as superseded. It walks the parent chain, so it costs
578
+ * the nesting depth: an ancestor rebuilds its value the next time one is asked of it, instead of being walked
579
+ * here for a reader that may never come. The walk also brings each element into the open transaction, so a
580
+ * rollback puts the versions back and no reader is left holding a cache the tree no longer supports.
581
+ */
582
+ protected bumpValueVersion(): void;
583
+ /**
584
+ * States that this element's value changed and that every container above it therefore holds a different value
585
+ * too. Nothing is announced here: the transaction works out at commit which of the elements enrolled actually
586
+ * ends up holding a different value, and announces those once each.
587
+ *
588
+ * `force` states that the caller already knows this element's value changed - one item more or fewer is a
589
+ * different list whatever the items compare as - so the commit announces it without composing a comparison.
590
+ */
591
+ protected propagateValueChanged(force?: boolean): void;
592
+ /**
593
+ * Re-reads the value that the next ValueChangedAction will report as the one it replaces. A leaf records what
594
+ * it announced at every announcement, so its copy is always current and there is nothing to re-read; a
595
+ * container that skipped composing a value nobody was listening for overrides this.
596
+ */
597
+ protected refreshPreviousValue(): void;
598
+ /**
599
+ * The verdict validate() records and announces: this element's own errors plus the tally of children that
600
+ * reported themselves invalid, so a container forms it without walking its members. `valid` is the read path
601
+ * and answers over the live errors arrays instead; the two differ only for an error written into a member
602
+ * without a validate() call, which is the documented cost of pushing errors by hand.
603
+ */
604
+ protected get countedValid(): boolean;
605
+ /**
606
+ * Makes this element the container of `child`: it installs the back-reference, records the name a Group holds
607
+ * the child under, and takes the child into the invalid tally. An element belongs to one container at a time,
608
+ * so a child that already carries the link is refused; releaseChild() takes the link away again, and an element
609
+ * released that way can be handed on. The child's state is unreachable from outside FieldBase, so a container
610
+ * writes the two slots through here rather than on the child.
611
+ */
612
+ protected takeChild(child: FieldBase, fieldName?: string): void;
613
+ /** Takes a child into this element's invalid tally; a container calls it once the child's parent link exists. */
614
+ protected adoptChild(child: FieldBase): void;
615
+ /**
616
+ * Drops a child out of this element's invalid tally and takes its back-reference away. An element keeps no link
617
+ * to a container that no longer holds it: it would go on moving that container's tally, and the link is what
618
+ * stands in the way of the element being taken by another container.
619
+ */
620
+ protected releaseChild(child: FieldBase): void;
621
+ /**
622
+ * Brings this element to the state a fresh binding of `source` carrying `value` would be in: `value` is written
623
+ * where the caller supplied one and `source`'s own value where it did not, the change history - originalValue,
624
+ * touched - starts over and the errors are dropped and re-established by the validators. A container that reuses
625
+ * an element at a position it already holds calls it, so the element is indistinguishable from one built for
626
+ * that position.
627
+ */
628
+ protected resetTo(source: FieldBase, value: any): void;
629
+ /** Resets a member. It is what lets a container reach the reset of a child whose class it does not know. */
630
+ protected resetChild(child: FieldBase, source: FieldBase, value: any): void;
631
+ /**
632
+ * Records a child's new verdict in this element's tally. The child reports it as it settles, and the commit
633
+ * settles the deepest element first, so the tally a container reads when its own turn comes is finished.
634
+ */
635
+ protected childValidityChanged(nowValid: boolean): void;
636
+ /**
637
+ * The composed verdict a container answers `valid` with, memoised by Vue. The walk over the members is what
638
+ * makes an error pushed into one of them visible without a validate() call, and the computed keeps that walk
639
+ * from repeating while nothing it read has moved.
640
+ */
641
+ protected get validRead(): boolean;
642
+ /** What validRead computes. A leaf answers over its own errors; a container adds its members. */
643
+ protected composeValid(): boolean;
176
644
  get visibility(): DisplayMode;
177
645
  set visibility(newValue: DisplayMode);
178
- private _enabled;
179
646
  get enabled(): boolean;
180
647
  set enabled(newValue: boolean);
181
648
  /**
182
- * While true, validate() on this field does nothing at all: it neither recomputes _valid nor emits
183
- * ValidChangedAction, and nothing climbs to the parent. A container that walks its members one by one - assigning
184
- * them a value, or revalidating them - sets it for the duration of the walk, so a member's upward climb cannot
185
- * announce the verdict of a half-applied value or of a half-revalidated set, and clears it in a finally block
186
- * followed by its own validate(). That call recomputes over the finished state and emits the net transition, so
187
- * the deferral cannot swallow a real change.
649
+ * True for an element whose value is composed of its members'. The commit builds such a value only where
650
+ * something receives it, compares it by content rather than by identity, and runs the element's own validators
651
+ * with the announcement: a container's validators read the composed value, which exists only once the members
652
+ * have been written. A leaf's validators have run at the write instead, so its announcement carries no eager
653
+ * pass of its own.
188
654
  */
189
- protected suppressValidation: boolean;
655
+ protected get composesValue(): boolean;
190
656
  /**
191
- * While true, a validity change is still recomputed and still announced on this field, but it is not passed up to
192
- * the parent: the climb is only remembered and carried out by flushParentValidityClimb(). A setter that notifies
193
- * the parent of the new value itself holds it over the notifying region, so the parent forms its verdict once,
194
- * over the finished value, instead of once over the value being replaced and once over the value replacing it.
657
+ * Announces what this element's value became over the transaction. The pair carried is (value now, value at the
658
+ * last announcement), so a value that went A -> B -> A within the transaction says nothing, and the events an
659
+ * operation states rather than an element's state - an item added, an item removed - are emitted first, in the
660
+ * order the operations happened.
195
661
  */
196
- protected suppressParentValidityClimb: boolean;
197
- private parentValidityClimbPending;
198
- /** Carries out the climb that a validity change made while suppressParentValidityClimb was held, if there was one. */
199
- protected flushParentValidityClimb(): void;
662
+ protected [TxAnnounceValue](tx: Transaction, dirty: boolean, force: boolean, structural?: TxStructuralEvent[]): void;
663
+ /**
664
+ * Forms the verdict this element ends the transaction with and announces a transition of it. The container is
665
+ * told first, so a handler reads a tally that already includes this verdict, and it is enrolled in the same
666
+ * transaction: its own verdict is composed of its members', so a member that moves without a value change - an
667
+ * asynchronous validator, an error pushed in by hand - is what makes the container re-form its own. The
668
+ * transaction settles the deepest element first, so a container is reached only once its members have decided.
669
+ */
670
+ protected [TxSettleValidity](tx: Transaction): void;
200
671
  validate(revalidate?: boolean): void;
201
672
  get valid(): boolean;
202
673
  get fullValue(): any;
203
674
  get isChanged(): boolean;
675
+ /**
676
+ * Brings a fresh element into the state a binding of `source` is in: it records what it was declared as, takes
677
+ * on the extended properties and the actions `source` carries, and runs the eager ones over the value it was
678
+ * built with. The extended properties `overrides` states are written over the ones `source` carries, and they
679
+ * are in place before the eager pass, so an action reading one sees what the binding ends up carrying. The
680
+ * actions are the very instances `source` holds - what a binding takes on is data, not behaviour - and each is
681
+ * told about this element as it is taken on, so an action serving several bindings knows all of them.
682
+ */
683
+ protected boundFrom(source: FieldBase<any, X>, newValue: any, oldValue: any, overrides?: object): void;
204
684
  /**
205
685
  * Registers the actions a constructor received in its parameters: registration without the eager trigger,
206
686
  * because a constructor ends with a single this.actions.triggerEager(...) over the finished value, and an
@@ -208,10 +688,28 @@ export declare abstract class FieldBase<T = any> {
208
688
  */
209
689
  protected registerInitialActions(actions: FieldActionBase[]): void;
210
690
  registerAction(action: FieldActionBase): this;
691
+ /**
692
+ * Registers `action` so that `before` wraps it: `before` reaches it through the `supr` it is handed, instead of
693
+ * ending the run there. It is how an action is added to a chain someone else built and still sits inside a
694
+ * handler already registered, which registration order alone cannot arrange. `before` has to be registered on
695
+ * this element under the same identifier.
696
+ */
697
+ registerActionBefore(action: FieldActionBase, before: FieldActionBase): this;
698
+ /**
699
+ * Drops `action` from this element and answers whether it held it. The instance goes on serving every other
700
+ * element it was registered on: what is dropped is this element's registration, not the action. A validator
701
+ * withdraws the errors it put on this element as it goes, so the verdict the element reports is the one the
702
+ * validators it still holds reach.
703
+ */
704
+ unregisterAction(action: FieldActionBase): boolean;
705
+ /**
706
+ * Records that `action` is now registered on this element, and that a rollback has to take that back. The
707
+ * registration is not one of the state slots the snapshot covers, so it hands its own undo in.
708
+ */
709
+ private registered;
211
710
  triggerAction<T2 extends FieldActionBase>(actionClass: (abstract new (...args: any[]) => T2) & {
212
711
  classIdentifier: symbol;
213
712
  }, ...params: any[]): any;
214
- private _validationEpoch;
215
713
  /**
216
714
  * Generation counter of the validators attached to this field. A Validator reads it when a run starts and drops
217
715
  * a result whose epoch no longer matches, so a validation still in flight when clearValidators() is called
@@ -221,6 +719,12 @@ export declare abstract class FieldBase<T = any> {
221
719
  clearValidators(): void;
222
720
  }
223
721
 
722
+ /** what a Field holds beyond the common slots: the value itself, and whether it has been touched */
723
+ declare interface FieldSlots<T = any> extends ElementSlots<T> {
724
+ value: T;
725
+ touched: boolean;
726
+ }
727
+
224
728
  export declare type FieldsToValues<T extends GenericFieldsInterface> = {
225
729
  [K in keyof T]: T[K]['value'];
226
730
  };
@@ -228,6 +732,8 @@ export declare type FieldsToValues<T extends GenericFieldsInterface> = {
228
732
  declare namespace Form {
229
733
  export {
230
734
  DisplayMode,
735
+ transaction,
736
+ TransactionControl,
231
737
  FieldActionBase,
232
738
  ActionsMap,
233
739
  EnabledChangedAction,
@@ -255,6 +761,8 @@ declare namespace Form {
255
761
  EmptyField,
256
762
  IFieldConstructorActionsList,
257
763
  IFieldConstructorParams,
764
+ IFieldParams,
765
+ IBindParams,
258
766
  AbortEventHandlingException,
259
767
  FieldActionExecute,
260
768
  FieldBase,
@@ -285,6 +793,7 @@ declare namespace Form {
285
793
  RenderableValue,
286
794
  ValidationFunctionResult,
287
795
  ValidationFunction,
796
+ ValidatorBindingState,
288
797
  Validator
289
798
  }
290
799
  }
@@ -300,33 +809,60 @@ declare interface FormsConfig {
300
809
 
301
810
  export declare type GenericFieldsInterface = Record<string, FieldBase>;
302
811
 
303
- export declare class Group<T extends GenericFieldsInterface = GenericFieldsInterface> extends FieldBase<GroupValue<T>> {
812
+ export declare class Group<T extends GenericFieldsInterface = GenericFieldsInterface, X extends object = {}> extends FieldBase<GroupValue<T>, X> {
813
+ protected get state(): ContainerSlots<GroupValue<T>>;
814
+ protected get raw(): ContainerSlots<GroupValue<T>>;
304
815
  private readonly _fields;
305
- private _value;
306
- private suppressNotifyValueChanged;
307
- constructor(fields: T, params?: Partial<IFieldConstructorParams<GroupValueInput<T>>>);
816
+ constructor(fields: T, params?: IFieldParams<GroupValueInput<T>, X>);
817
+ /**
818
+ * The copy of a built value that serves as a baseline. The value getter hands out one object per version, and
819
+ * a baseline holding that same object would report every value as its own original.
820
+ */
821
+ private static baseline;
308
822
  private addField;
309
823
  private static isValidFields;
310
824
  static createFromFormData(data: Record<string, any> | null): Group;
311
825
  field<K extends keyof T>(fieldName: K): T[K] | null;
312
826
  get fields(): T;
827
+ protected get members(): FieldBase[];
313
828
  get value(): GroupValue<T>;
314
829
  /**
315
- * Writes the members that the given value carries, without letting any single member announce the state in
316
- * between: the group's own value notification and its own validation are both held back for the duration, so
317
- * neither a ValueChangedAction nor a ValidChangedAction reports a verdict over a half-applied value. The
318
- * caller decides what to announce once the whole value is in place. Both flags are restored to what they were,
319
- * so a caller that is itself holding the group back stays in charge.
830
+ * Writes the members that the given value carries. The members are written one by one and the group says
831
+ * nothing in between: the transaction the assignment runs in measures the group's own value and its own verdict
832
+ * once, over the finished set, and announces each at most once.
320
833
  */
321
834
  private assignMembers;
322
835
  set value(newValue: GroupValueInput<T>);
836
+ /**
837
+ * Members are reset one by one rather than assigned as a whole value: the value setter writes only the keys the
838
+ * value carries, while a member the value leaves out has to end up holding what the template gives it. `source`
839
+ * supplies that per member, so a group reset from the template it was bound from matches a fresh binding of it.
840
+ */
841
+ protected resetTo(source: FieldBase, value: any): void;
323
842
  get touched(): boolean;
324
843
  set touched(touched: boolean);
325
844
  get fullValue(): Record<string, any>;
845
+ /**
846
+ * Records that a member changed its value, so that the transaction in progress works out at commit what this
847
+ * group's own value became and announces it once. A mutation method calls it itself; you rarely need to.
848
+ */
326
849
  notifyValueChanged(): void;
850
+ protected get composesValue(): boolean;
851
+ /**
852
+ * A group with nothing listening for its value does not compose one at all, so the copy it holds is from before
853
+ * the changes nobody received. A registration that adds a listener brings it up to date here, and what the
854
+ * listener is then told about is the change that follows it.
855
+ */
856
+ protected refreshPreviousValue(): void;
327
857
  get valid(): boolean;
858
+ protected composeValid(): boolean;
328
859
  validate(revalidate?: boolean): void;
329
- clone(overrides?: Partial<IFieldConstructorParams<GroupValueInput<T>>>): Group<T>;
860
+ bind(data?: GroupValueInput<T>, overrides?: IBindParams<GroupValueInput<T>, X>): Group<T, X>;
861
+ /**
862
+ * A record need not name every member: a key it leaves out is taken from the declaration, the same way a member
863
+ * the constructor is given no value for takes the one it was declared with.
864
+ */
865
+ rebind(data: GroupValueInput<T>): this;
330
866
  }
331
867
 
332
868
  /** what Group.value reads back: the full field map, or null when no field serializes */
@@ -335,17 +871,25 @@ export declare type GroupValue<T extends GenericFieldsInterface> = FieldsToValue
335
871
  /** what Group.value and the Group constructor accept: keys left out are simply not assigned */
336
872
  export declare type GroupValueInput<T extends GenericFieldsInterface> = Partial<FieldsToValues<T>> | null;
337
873
 
874
+ /**
875
+ * What bind() accepts beside the data it binds: everything a constructor takes except the value, which the data
876
+ * argument states. Of the members named here, bind() reads `originalValue`, `enabled` and `visibility`;
877
+ * `validators` and `actions` state what to register and are carried by the binding from its declaration, and the
878
+ * rest are the element's to establish.
879
+ */
880
+ export declare type IBindParams<T = any, X extends object = {}> = Partial<Omit<IFieldConstructorParams<T>, 'value'>> & Partial<NoInfer<X>>;
881
+
338
882
  export declare interface IFieldConstructorActionsList {
339
883
  actions?: FieldActionBase[];
340
884
  validators?: FieldActionBase[];
341
885
  }
342
886
 
343
887
  /**
344
- * Parameters accepted by field constructors and by clone overrides.
888
+ * Parameters accepted by field constructors and by bind overrides.
345
889
  *
346
890
  * Only writable members are listed. valid, validating, fullValue and isChanged are getter-only, so assigning
347
- * them throws a TypeError. parent and fieldName are set by the container: until a Group or List installs them
348
- * as non-configurable accessors, assigning them is silently accepted, which is why the type rejects them.
891
+ * them throws a TypeError. So are parent and fieldName: a container writes the slots behind them when it takes
892
+ * an element, and nobody else can, which is why the type rejects them.
349
893
  */
350
894
  export declare type IFieldConstructorParams<T = any> = {
351
895
  value: T;
@@ -356,6 +900,20 @@ export declare type IFieldConstructorParams<T = any> = {
356
900
  errors: ValidationError[];
357
901
  } & IFieldConstructorActionsList;
358
902
 
903
+ /**
904
+ * What an element's constructor accepts: the parameters the element answers for itself, together with the
905
+ * extended properties its X parameter declares.
906
+ *
907
+ * X is left out of inference, so an element built without a type argument carries no extended properties and a
908
+ * parameter object naming one is rejected as an excess property. A caller that wants them states them:
909
+ * `new Field<string, { label: string }>({ value: 'a', label: 'Name' })`.
910
+ *
911
+ * The two halves are made partial separately. `Partial<A & X>` is a mapped type over an intersection, which is
912
+ * what T is inferred through, and inference through it answers with one constituent of a union rather than the
913
+ * union: `new Field({ value: someStringOrNumber })` would be a Field<string>.
914
+ */
915
+ export declare type IFieldParams<T = any, X extends object = {}> = Partial<IFieldConstructorParams<T>> & Partial<NoInfer<X>>;
916
+
359
917
  declare class InAllowedValues<T = any> extends Validator {
360
918
  constructor(allowedValues: T[], message?: RenderContentRef);
361
919
  }
@@ -373,31 +931,58 @@ declare class LengthInRange<T = any> extends Validator {
373
931
  constructor(minLength: number, maxLength: number, message?: RenderContentRef);
374
932
  }
375
933
 
376
- export declare class List<T extends GenericFieldsInterface = GenericFieldsInterface> extends FieldBase<ListValue> {
377
- private _value;
934
+ export declare class List<T extends GenericFieldsInterface = GenericFieldsInterface, X extends object = {}> extends FieldBase<ListValue, X> {
935
+ protected get state(): ListSlots<T>;
936
+ protected get raw(): ListSlots<T>;
378
937
  private _itemTemplate?;
379
- private _previousValue;
380
- constructor(itemTemplate?: Group<T>, params?: Partial<IFieldConstructorParams<ListValue>>);
938
+ constructor(itemTemplate?: Group<T>, params?: IFieldParams<ListValue, X>);
939
+ /** The row array is copied as well: a rollback puts back the set the list held, not the array it went on to hold. */
940
+ protected [TxCapture](): TxSnapshot;
941
+ /**
942
+ * The copy of a built value that serves as a baseline. The value getter hands out one array per version, and a
943
+ * baseline holding that same array would report every value as its own original.
944
+ */
945
+ private static baseline;
381
946
  private processSetValueItem;
382
947
  /**
383
- * Builds the item that fills a gap left by an insert beyond the end of the list: a copy of the item template with
384
- * the template's own values, or an empty group when the list has no template.
948
+ * Builds the item that fills a gap left by an insert beyond the end of the list: the item template bound to its
949
+ * own values, or an empty group when the list has no template.
385
950
  */
386
951
  private createPaddingItem;
387
952
  private setValueInternal;
388
953
  get value(): ListValue;
389
954
  set value(newValue: Record<string, any>[]);
955
+ /**
956
+ * The rows are taken from `source` where the caller supplied no value of its own, so a list nested in a row that
957
+ * a whole-list assignment reuses ends up holding what the template gives it rather than what it held before.
958
+ */
959
+ protected resetTo(source: FieldBase, value: any): void;
390
960
  get touched(): boolean;
391
961
  set touched(touched: boolean);
392
- clone(overrides?: Partial<IFieldConstructorParams<ListValue>>): List<T>;
962
+ bind(data?: ListValue, overrides?: IBindParams<ListValue, X>): List<T, X>;
963
+ /**
964
+ * Records that a row changed its value, so that the transaction in progress works out at commit what this
965
+ * list's own value became and announces it once. The mutation methods call it themselves; you rarely need to.
966
+ */
393
967
  notifyValueChanged(): void;
968
+ protected get composesValue(): boolean;
969
+ /**
970
+ * A list with nothing listening for its value does not compose one at all, so the copy it holds is from before
971
+ * the changes nobody received. A registration that adds a listener brings it up to date here, and what the
972
+ * listener is then told about is the change that follows it.
973
+ */
974
+ protected refreshPreviousValue(): void;
394
975
  get valid(): boolean;
976
+ protected composeValid(): boolean;
395
977
  validate(revalidate?: boolean): void;
978
+ protected get members(): FieldBase[];
396
979
  get(index: number): Group<T> | undefined;
397
980
  push(item: any): number;
398
981
  pop(): Group<T> | undefined;
399
982
  remove(index: number): Group<T> | undefined;
400
983
  insert(item: any, index: number): number;
984
+ /** Drops every row this list holds out of its tally, so a row that changes its verdict later is not counted. */
985
+ private releaseRows;
401
986
  clear(): void;
402
987
  }
403
988
 
@@ -413,6 +998,11 @@ export declare class ListItemRemovedAction extends FieldActionBase {
413
998
  execute(field: FieldBase, supr: FieldActionExecute, item: any, index: number): void;
414
999
  }
415
1000
 
1001
+ /** what a List holds beyond the container slots: the rows themselves */
1002
+ declare interface ListSlots<T extends GenericFieldsInterface = GenericFieldsInterface> extends ContainerSlots<ListValue> {
1003
+ rows: Group<T>[] | null;
1004
+ }
1005
+
416
1006
  /** what List.value reads back: one plain object per item, or null when the list is empty */
417
1007
  export declare type ListValue = Record<string, any>[] | null;
418
1008
 
@@ -530,9 +1120,20 @@ export declare class Statement {
530
1120
  private readonly operator;
531
1121
  private readonly operand2;
532
1122
  constructor(operand1: OperandType, operator: Operator, operand2: OperandType);
1123
+ /**
1124
+ * The value an operand contributes. A field operand names a field of the record the statement is evaluated in:
1125
+ * the same statement over a `List` reads row 3's field while row 3 is the record, and a field belonging to no
1126
+ * record of the statement's own - a form field above the list - is read where it stands. A record that does not
1127
+ * hold the field yet contributes undefined, which is what a half-built row answers with.
1128
+ */
1129
+ private static valueOf;
533
1130
  get operand1Value(): any;
534
1131
  get operand2Value(): any;
535
- evaluate(): boolean;
1132
+ /**
1133
+ * The result of the statement, over the record `scope` belongs to where one is given and over the fields the
1134
+ * statement was built from where it is not.
1135
+ */
1136
+ evaluate(scope?: FieldBase): boolean;
536
1137
  /**
537
1138
  * Recursively collects all fields used in this statement and its nested statements
538
1139
  * @returns A set of all fields used in this statement
@@ -540,6 +1141,147 @@ export declare class Statement {
540
1141
  collectFields(): Set<FieldBase>;
541
1142
  }
542
1143
 
1144
+ declare class Transaction {
1145
+ private readonly participants;
1146
+ /** what a rollback has to put back beyond the state slots, newest last */
1147
+ private readonly undo;
1148
+ /** true once the transaction has been unwound, which is what tells a run started inside it to say nothing */
1149
+ private unwound;
1150
+ /** the depth the announcement in progress is working at, -1 while none is */
1151
+ private passDepth;
1152
+ /** set while a pass is running and an element it has already passed the depth of gets enrolled */
1153
+ private restartPass;
1154
+ /** true for a transaction that was rolled back rather than committed */
1155
+ get rolledBack(): boolean;
1156
+ private participant;
1157
+ /**
1158
+ * Records the state of an element the first time the transaction modifies it. The whole of the mutable state
1159
+ * goes into the snapshot rather than the part being written: a rollback that restored some of it would leave
1160
+ * the element in a state the form never held.
1161
+ */
1162
+ touch(element: FieldBase): void;
1163
+ /** Enrols an element as one whose value the commit has to announce. `force` skips the comparison. */
1164
+ markValueChanged(element: FieldBase, force: boolean): void;
1165
+ /** Enrols an element as one whose verdict the commit has to re-form. */
1166
+ markValidityDirty(element: FieldBase): void;
1167
+ /**
1168
+ * Queues an event that states an operation rather than a state. Added and removed items have no net over a
1169
+ * transaction, so they are neither compared nor coalesced: the commit emits them in the order they happened.
1170
+ */
1171
+ recordStructural(element: FieldBase, event: TxStructuralEvent): void;
1172
+ /** True where the commit still owes this element a value announcement. */
1173
+ willAnnounceValue(element: FieldBase): boolean;
1174
+ /**
1175
+ * Registers what a rollback has to put back beyond the element's state slots. The snapshot covers the slots,
1176
+ * which is everything an ordinary write touches; an operation that replaces something else the element carries
1177
+ * hands its own undo in here rather than making every snapshot carry the thing.
1178
+ */
1179
+ whenRolledBack(work: () => void): void;
1180
+ /**
1181
+ * Notes that an element at or below the depth the pass in progress is working at has been enrolled. The pass
1182
+ * then gives up the rest of its batch and is started again over what is now owed, so the newcomer is reached
1183
+ * before the containers above it: an element a handler writes must still be announced before the container
1184
+ * whose composed value carries that write. The depth is only measured while a pass is running - outside one
1185
+ * there is nothing the answer would change.
1186
+ */
1187
+ private noteDepth;
1188
+ /**
1189
+ * Puts every element the transaction modified back as it found it. Nothing is announced: from an observer's
1190
+ * point of view the transaction never happened. What a rollback cannot take back are side effects - a handler
1191
+ * that called a server during the transaction already did.
1192
+ */
1193
+ rollback(): void;
1194
+ /**
1195
+ * Announces what the transaction did. Values first and verdicts after, because a container's validators run
1196
+ * with its value announcement and the verdict they reach is what the validity pass then reports. Both passes
1197
+ * run deepest first - field, then row, then list - which is the order the change travelled in.
1198
+ *
1199
+ * A handler may write while the commit runs; its writes join this transaction, so both passes repeat until
1200
+ * nothing is left dirty.
1201
+ */
1202
+ commit(): void;
1203
+ /**
1204
+ * The dirty participants a pass has to visit, one bucket per nesting depth; ties keep the order they were
1205
+ * enrolled in. A nesting depth is a small integer, so the elements go into buckets rather than through a
1206
+ * comparison sort: a whole-list assignment enrols one participant per field, and ordering those by comparison
1207
+ * would cost more than the announcement it orders.
1208
+ */
1209
+ private buckets;
1210
+ /**
1211
+ * Runs one pass over the dirty participants, deepest bucket first; the caller repeats it until it answers
1212
+ * false. The buckets are taken once, and the pass gives up as soon as a handler enrols an element at or below
1213
+ * the depth being drained: that element is not in the bucket being walked, and going on would reach the
1214
+ * containers above it first. The caller's next pass takes fresh buckets and finds it at its own depth.
1215
+ */
1216
+ private pass;
1217
+ private announceValues;
1218
+ private settleValidity;
1219
+ }
1220
+
1221
+ /**
1222
+ * Runs `fn` as one atomic change. The writes it makes land in the elements as they are made and the events they
1223
+ * produce are announced once, at the end, over the net result.
1224
+ *
1225
+ * A call made while a transaction is already open joins it: nothing is committed until the outermost call
1226
+ * returns. A throw out of `fn` rolls the whole transaction back and rethrows, and `tx.rollback()` rolls it back
1227
+ * without an error, in which case the call answers undefined.
1228
+ *
1229
+ * The handle `fn` receives is usable only for the duration of that call; calling it afterwards throws a TypeError.
1230
+ *
1231
+ * @throws TypeError immediately if `fn` returns a thenable - a transaction cannot cross an await.
1232
+ */
1233
+ export declare function transaction<R>(fn: (tx: TransactionControl) => R): R | undefined;
1234
+
1235
+ export declare interface TransactionControl {
1236
+ /**
1237
+ * Undoes everything the transaction has done and ends it, announcing nothing. It unwinds from the point of
1238
+ * the call, so nothing after it runs. There are no savepoints: a nested call rolls back the whole transaction
1239
+ * it joined, not its own part of it.
1240
+ *
1241
+ * @throws TypeError where the transaction the handle belongs to has already closed.
1242
+ */
1243
+ rollback(): never;
1244
+ }
1245
+
1246
+ declare const TxAnnounceValue: unique symbol;
1247
+
1248
+ /**
1249
+ * The protocol a transaction drives its participants through. The four members are symbol-keyed so that they
1250
+ * carry no name a consumer could reach or collide with, and they live on the prototype, so no walker over an
1251
+ * element's own keys sees them either.
1252
+ */
1253
+ declare const TxCapture: unique symbol;
1254
+
1255
+ declare const TxRestore: unique symbol;
1256
+
1257
+ declare const TxSettleValidity: unique symbol;
1258
+
1259
+ /**
1260
+ * A transaction is the unit an observer sees a change in. Every mutating operation runs inside one: where the
1261
+ * caller opens none, the operation is the transaction, so a single write is atomic without anything having to be
1262
+ * remembered at the call site.
1263
+ *
1264
+ * Writes land in the element straight away and only the announcement waits. At commit the net transitions are
1265
+ * measured against the state the elements last announced and each is announced once, so a value that goes A -> B
1266
+ * -> A within one transaction says nothing. Validators run while the transaction is open, because the verdict
1267
+ * they reach is what the commit announces; every other action fires at commit.
1268
+ *
1269
+ * A transaction may not cross an await: transaction() throws when the callback returns a thenable. Two
1270
+ * overlapping transactions therefore cannot exist, and an asynchronous validator settling later opens one of its
1271
+ * own at that moment.
1272
+ */
1273
+ /** the slots of one element as they stood when the transaction first modified it */
1274
+ declare type TxSnapshot = Record<string, any>;
1275
+
1276
+ /** a ListItemAddedAction or ListItemRemovedAction waiting for the commit that will emit it */
1277
+ declare interface TxStructuralEvent {
1278
+ actionClass: (abstract new (...args: any[]) => FieldActionBase) & {
1279
+ classIdentifier: symbol;
1280
+ };
1281
+ item: any;
1282
+ index: number;
1283
+ }
1284
+
543
1285
  /**
544
1286
  * Base validation error class with component rendering capabilities
545
1287
  */
@@ -588,13 +1330,6 @@ export declare type ValidationFunctionResult = ValidationError[] | null;
588
1330
  */
589
1331
  export declare class Validator<T = any> extends ValueChangedAction {
590
1332
  private readonly source;
591
- /**
592
- * Sequence number of the newest run of this validator, per field. Every execution takes the next number, and a
593
- * result is applied only while its number is still the newest one, so a slow run cannot overwrite the verdict of
594
- * a faster one that started after it. The key is the field because one validator instance may be registered on
595
- * several fields, each with a sequence of its own.
596
- */
597
- private readonly runs;
598
1333
  /**
599
1334
  * Creates a new validator
600
1335
  * @param validationFn Function that validates the field value and returns errors or null
@@ -610,18 +1345,43 @@ export declare class Validator<T = any> extends ValueChangedAction {
610
1345
  private claim;
611
1346
  static get classIdentifier(): symbol;
612
1347
  get eager(): boolean;
1348
+ /**
1349
+ * Takes this validator off `field`: the errors it put there are withdrawn and the verdict is re-formed over what
1350
+ * the validators the field still holds have to say. Withdrawing them is what keeps a field from staying invalid
1351
+ * on an error no validator is left to take back.
1352
+ */
1353
+ unregisterFrom(field: FieldBase): void;
1354
+ /**
1355
+ * What this validator remembers about one of the fields it validates. One instance may be registered on several
1356
+ * fields - every row of a list carries the instances the item template carries - so each field has a record of
1357
+ * its own. A subclass that remembers more widens the record by overriding `newBindingState`.
1358
+ */
1359
+ protected bindingState(field: FieldBase<any>): ValidatorBindingState;
1360
+ /**
1361
+ * The record a field starts with. `run` is the sequence number of the newest run over that field: every
1362
+ * execution takes the next one and a result is applied only while its number is still the newest, so a slow run
1363
+ * cannot overwrite the verdict of a faster one that started after it.
1364
+ */
1365
+ protected newBindingState(): ValidatorBindingState;
613
1366
  protected replacePlaceholdersFunction(text: RenderContentRef, replace: Record<string, any>): RenderContentRef;
614
1367
  protected replacePlaceholders(text: RenderContentRef, replace: Record<string, any>): RenderContentRef;
615
1368
  }
616
1369
 
1370
+ /** What a validator remembers about one field it is registered on; a subclass widens it. */
1371
+ export declare interface ValidatorBindingState {
1372
+ run: number;
1373
+ }
1374
+
617
1375
  export declare namespace Validators {
618
1376
  export {
619
1377
  CompareTo,
1378
+ CompareToTarget,
620
1379
  InAllowedValues,
621
1380
  Required_2 as Required,
622
1381
  Pattern,
623
1382
  ValidationFunctionResult,
624
1383
  ValidationFunction,
1384
+ ValidatorBindingState,
625
1385
  Validator,
626
1386
  MinValue,
627
1387
  MaxValue,