adaptform 2.1.0 → 2.2.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.mjs CHANGED
@@ -1,11 +1,332 @@
1
+ // src/core/state/value.ts
2
+ function copyValue(value, seen = /* @__PURE__ */ new WeakMap()) {
3
+ if (value === null || typeof value !== "object") return value;
4
+ if (seen.has(value)) return seen.get(value);
5
+ const remember = (copy) => {
6
+ seen.set(value, copy);
7
+ return copy;
8
+ };
9
+ if (value instanceof Date) return remember(new Date(value.getTime()));
10
+ if (value instanceof RegExp) return remember(new RegExp(value.source, value.flags));
11
+ if (value instanceof ArrayBuffer) return remember(value.slice(0));
12
+ if (ArrayBuffer.isView(value)) {
13
+ const bytes = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
14
+ return remember(value instanceof DataView ? new DataView(bytes) : new value.constructor(bytes));
15
+ }
16
+ if (value instanceof Map) {
17
+ const result = /* @__PURE__ */ new Map();
18
+ seen.set(value, result);
19
+ for (const [key, item] of value) result.set(copyValue(key, seen), copyValue(item, seen));
20
+ return result;
21
+ }
22
+ if (value instanceof Set) {
23
+ const result = /* @__PURE__ */ new Set();
24
+ seen.set(value, result);
25
+ for (const item of value) result.add(copyValue(item, seen));
26
+ return result;
27
+ }
28
+ if (Array.isArray(value) || Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null) {
29
+ const result = Array.isArray(value) ? new Array(value.length) : Object.create(Object.getPrototypeOf(value));
30
+ seen.set(value, result);
31
+ for (const key of Object.keys(value)) Object.defineProperty(result, key, {
32
+ value: copyValue(value[key], seen),
33
+ enumerable: true,
34
+ writable: true,
35
+ configurable: true
36
+ });
37
+ return result;
38
+ }
39
+ return value;
40
+ }
41
+ function equalValue(a, b, seen = /* @__PURE__ */ new WeakMap()) {
42
+ if (Object.is(a, b)) return true;
43
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
44
+ if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
45
+ if (seen.get(a)?.has(b)) return true;
46
+ const matches = seen.get(a) ?? /* @__PURE__ */ new WeakSet();
47
+ matches.add(b);
48
+ seen.set(a, matches);
49
+ if (a instanceof Date && b instanceof Date) return Object.is(a.getTime(), b.getTime());
50
+ if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags;
51
+ if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) return equalValue(new Uint8Array(a), new Uint8Array(b), seen);
52
+ if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
53
+ const av = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
54
+ const bv = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
55
+ return av.length === bv.length && av.every((byte, i) => byte === bv[i]);
56
+ }
57
+ if (a instanceof Map && b instanceof Map) return equalValue([...a], [...b], seen);
58
+ if (a instanceof Set && b instanceof Set) return equalValue([...a], [...b], seen);
59
+ if ("isLuxonDateTime" in a && a.isLuxonDateTime === true && "equals" in a && typeof a.equals === "function") {
60
+ return a.equals(b);
61
+ }
62
+ if (!Array.isArray(a) && Object.getPrototypeOf(a) !== Object.prototype && Object.getPrototypeOf(a) !== null) return false;
63
+ if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length) return false;
64
+ const keys = Object.keys(a);
65
+ return keys.length === Object.keys(b).length && keys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && equalValue(a[key], b[key], seen));
66
+ }
67
+
68
+ // src/core/state/snapshot.ts
69
+ function snapshotValue(value, seen = /* @__PURE__ */ new WeakMap()) {
70
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value;
71
+ if (typeof value !== "object") return Object.freeze({ kind: "Opaque", type: typeof value });
72
+ const previous = seen.get(value);
73
+ if (previous) return previous;
74
+ if (value instanceof Date) return Object.freeze({ kind: "Date", timestamp: value.getTime() });
75
+ if (value instanceof RegExp) return Object.freeze({ kind: "RegExp", source: value.source, flags: value.flags });
76
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
77
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
78
+ return Object.freeze({ kind: value.constructor.name, bytes: Object.freeze([...bytes]) });
79
+ }
80
+ if ("isLuxonDateTime" in value && value.isLuxonDateTime === true && "toISO" in value && typeof value.toISO === "function") {
81
+ return Object.freeze({ kind: "DateTime", iso: value.toISO() });
82
+ }
83
+ if (value instanceof Map || value instanceof Set) {
84
+ const result = { kind: value instanceof Map ? "Map" : "Set" };
85
+ seen.set(value, result);
86
+ result.entries = snapshotValue([...value], seen);
87
+ return Object.freeze(result);
88
+ }
89
+ if (Array.isArray(value)) {
90
+ const result = [];
91
+ seen.set(value, result);
92
+ for (const item of value) result.push(snapshotValue(item, seen));
93
+ return Object.freeze(result);
94
+ }
95
+ if (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null) {
96
+ const result = /* @__PURE__ */ Object.create(null);
97
+ seen.set(value, result);
98
+ for (const [key, item] of Object.entries(value)) result[key] = snapshotValue(item, seen);
99
+ return Object.freeze(result);
100
+ }
101
+ return Object.freeze({ kind: "Opaque", type: value.constructor?.name ?? "object" });
102
+ }
103
+ function freezeSnapshot(value, seen = /* @__PURE__ */ new WeakSet()) {
104
+ if (value === null || typeof value !== "object" || seen.has(value)) return value;
105
+ seen.add(value);
106
+ for (const item of Object.values(value)) freezeSnapshot(item, seen);
107
+ return Object.freeze(value);
108
+ }
109
+
110
+ // src/core/state/ObservableNode.ts
111
+ var mutationDepth = 0;
112
+ var flushing = false;
113
+ var publishing = false;
114
+ var batchDepth = 0;
115
+ var validations = /* @__PURE__ */ new Map();
116
+ var batchRoots = /* @__PURE__ */ new Map();
117
+ var pending = /* @__PURE__ */ new Set();
118
+ function flush() {
119
+ if (mutationDepth || flushing) return;
120
+ flushing = true;
121
+ let failure;
122
+ let failed = false;
123
+ try {
124
+ while (pending.size) {
125
+ const stores = [...pending];
126
+ const before = stores.map((store) => store.committed());
127
+ pending.clear();
128
+ for (const store of stores) store.refresh();
129
+ const changed = stores.filter((store, index) => !equalValue(before[index], store.committed()));
130
+ publishing = true;
131
+ try {
132
+ for (const store of changed) {
133
+ try {
134
+ store.notify();
135
+ } catch (error) {
136
+ if (!failed) {
137
+ failure = error;
138
+ failed = true;
139
+ }
140
+ }
141
+ }
142
+ } finally {
143
+ publishing = false;
144
+ }
145
+ }
146
+ } finally {
147
+ flushing = false;
148
+ }
149
+ if (failed) throw failure;
150
+ }
151
+ var SnapshotStore = class {
152
+ constructor(capture, getChildren) {
153
+ this.capture = capture;
154
+ this.getChildren = getChildren;
155
+ this.initialized = false;
156
+ this.listeners = /* @__PURE__ */ new Set();
157
+ this.parents = /* @__PURE__ */ new Set();
158
+ this.children = /* @__PURE__ */ new Set();
159
+ }
160
+ reconcile(seen = /* @__PURE__ */ new Set()) {
161
+ if (seen.has(this)) return;
162
+ seen.add(this);
163
+ const next = new Set(this.listeners.size || this.parents.size ? this.getChildren() : []);
164
+ for (const child of this.children) {
165
+ if (!next.has(child)) {
166
+ child.parents.delete(this);
167
+ child.reconcile(seen);
168
+ }
169
+ }
170
+ const previous = this.children;
171
+ this.children = next;
172
+ for (const child of next) {
173
+ if (!previous.has(child)) {
174
+ child.parents.add(this);
175
+ child.reconcile(seen);
176
+ }
177
+ }
178
+ }
179
+ read() {
180
+ if (!mutationDepth && !publishing) this.refresh();
181
+ else if (!this.initialized) this.refresh();
182
+ return this.snapshot;
183
+ }
184
+ committed() {
185
+ return this.snapshot;
186
+ }
187
+ refresh() {
188
+ this.reconcile();
189
+ const next = freezeSnapshot(this.capture());
190
+ if (this.initialized && equalValue(this.snapshot, next)) return false;
191
+ this.snapshot = next;
192
+ this.initialized = true;
193
+ return true;
194
+ }
195
+ subscribe(listener) {
196
+ this.read();
197
+ const registration = () => listener();
198
+ this.listeners.add(registration);
199
+ this.reconcile();
200
+ return () => {
201
+ this.listeners.delete(registration);
202
+ this.reconcile();
203
+ };
204
+ }
205
+ changed(seen = /* @__PURE__ */ new Set()) {
206
+ if (seen.has(this)) return;
207
+ seen.add(this);
208
+ this.reconcile();
209
+ if (this.listeners.size || this.parents.size || this.initialized) pending.add(this);
210
+ for (const parent of this.parents) parent.changed(seen);
211
+ }
212
+ notify() {
213
+ let failure;
214
+ let failed = false;
215
+ for (const listener of [...this.listeners]) {
216
+ if (!this.listeners.has(listener)) continue;
217
+ try {
218
+ listener();
219
+ } catch (error) {
220
+ if (!failed) {
221
+ failure = error;
222
+ failed = true;
223
+ }
224
+ }
225
+ }
226
+ if (failed) throw failure;
227
+ }
228
+ };
229
+ var ObservableNode = class {
230
+ constructor() {
231
+ this._stateStore = new SnapshotStore(
232
+ () => this.captureState(),
233
+ () => this.stateChildren().map((child) => child._stateStore)
234
+ );
235
+ }
236
+ stateChildren() {
237
+ return [];
238
+ }
239
+ contains(node, seen = /* @__PURE__ */ new Set()) {
240
+ if (this === node) return true;
241
+ if (seen.has(this)) return false;
242
+ seen.add(this);
243
+ return this.stateChildren().some((child) => child.contains(node, seen));
244
+ }
245
+ validateAfterMutation(callback) {
246
+ if (batchDepth) validations.set(this, callback);
247
+ else callback();
248
+ }
249
+ batchUpdate(callback, validate) {
250
+ this.getSnapshot();
251
+ return this.update(() => {
252
+ batchDepth++;
253
+ batchRoots.set(this, validate);
254
+ let failed = false;
255
+ let failure;
256
+ let result;
257
+ try {
258
+ result = callback();
259
+ if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") {
260
+ throw new TypeError("batch callbacks must be synchronous");
261
+ }
262
+ } catch (error) {
263
+ failed = true;
264
+ failure = error;
265
+ } finally {
266
+ batchDepth--;
267
+ }
268
+ if (!batchDepth) {
269
+ const requested = [...validations];
270
+ const roots = [...batchRoots];
271
+ validations.clear();
272
+ batchRoots.clear();
273
+ const selected = roots.filter(([root]) => requested.some(([node]) => root.contains(node))).filter(([root], index, all) => !all.some(([other], otherIndex) => other !== root && other.contains(root) && (!root.contains(other) || otherIndex < index)));
274
+ const checks = [
275
+ ...selected.map(([, check]) => check),
276
+ ...requested.filter(([node]) => !selected.some(([root]) => root.contains(node))).map(([, check]) => check)
277
+ ];
278
+ for (const check of checks) {
279
+ try {
280
+ check();
281
+ } catch (error) {
282
+ if (!failed) {
283
+ failed = true;
284
+ failure = error;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ if (failed) throw failure;
290
+ return result;
291
+ });
292
+ }
293
+ subscribe(listener) {
294
+ return this._stateStore.subscribe(listener);
295
+ }
296
+ getSnapshot() {
297
+ return this._stateStore.read();
298
+ }
299
+ update(callback) {
300
+ mutationDepth++;
301
+ let failed = false;
302
+ try {
303
+ return callback();
304
+ } catch (error) {
305
+ failed = true;
306
+ throw error;
307
+ } finally {
308
+ this._stateStore.changed();
309
+ mutationDepth--;
310
+ if (failed) {
311
+ try {
312
+ flush();
313
+ } catch {
314
+ }
315
+ } else flush();
316
+ }
317
+ }
318
+ };
319
+
1
320
  // src/core/fields/Field.ts
2
- var Field = class {
321
+ var Field = class extends ObservableNode {
3
322
  constructor(TypeConstructor, defaultValue, options) {
323
+ super();
4
324
  this._rawValue = null;
5
325
  this._valueClear = null;
326
+ this._baseline = null;
6
327
  this._errors = [];
7
- this.isValid = null;
8
- this.isTouched = false;
328
+ this._isValid = null;
329
+ this._isTouched = false;
9
330
  this.fieldType = new TypeConstructor(options ?? {});
10
331
  this.defaultValue = defaultValue ?? null;
11
332
  for (const plugin of this.options.plugins ?? []) {
@@ -19,6 +340,33 @@ var Field = class {
19
340
  this.isValid = null;
20
341
  }
21
342
  this.isTouched = false;
343
+ this.commitBaseline();
344
+ }
345
+ get isValid() {
346
+ return this._isValid;
347
+ }
348
+ set isValid(value) {
349
+ this.update(() => {
350
+ this._isValid = value;
351
+ });
352
+ }
353
+ get isTouched() {
354
+ return this._isTouched;
355
+ }
356
+ set isTouched(value) {
357
+ this.update(() => {
358
+ this._isTouched = value;
359
+ });
360
+ }
361
+ captureState() {
362
+ return {
363
+ rawValue: snapshotValue(this._rawValue),
364
+ valueClear: snapshotValue(this._valueClear),
365
+ errors: [...this._errors],
366
+ isValid: this.isValid,
367
+ isTouched: this.isTouched,
368
+ isDirty: this.isDirty
369
+ };
22
370
  }
23
371
  // ========== Getters and setters ==========
24
372
  get plugins() {
@@ -30,8 +378,10 @@ var Field = class {
30
378
  return this.fieldType.options;
31
379
  }
32
380
  set options(options) {
33
- const currentOptions = this.fieldType.options;
34
- this.fieldType.options = Object.assign(currentOptions, options);
381
+ this.update(() => {
382
+ const currentOptions = this.fieldType.options;
383
+ this.fieldType.options = Object.assign(currentOptions, options);
384
+ });
35
385
  }
36
386
  get rawValue() {
37
387
  return this._rawValue;
@@ -40,14 +390,16 @@ var Field = class {
40
390
  this.setRawValue(input);
41
391
  }
42
392
  setRawValue(input, validate = true) {
43
- this.assignRawValue(input);
44
- this._errors = [];
45
- this.isTouched = true;
46
- if (validate) {
47
- this.checkAndSetValid();
48
- } else {
49
- this.isValid = null;
50
- }
393
+ return this.update(() => {
394
+ this.assignRawValue(input);
395
+ this._errors = [];
396
+ this.isTouched = true;
397
+ if (validate) {
398
+ this.validateAfterMutation(() => this.checkAndSetValid());
399
+ } else {
400
+ this.isValid = null;
401
+ }
402
+ });
51
403
  }
52
404
  assignRawValue(input) {
53
405
  let rawValue = input;
@@ -72,16 +424,26 @@ var Field = class {
72
424
  return this._errors[0] ?? "";
73
425
  }
74
426
  set error(message) {
75
- if (message) {
76
- this._errors = [message];
77
- } else {
78
- this._errors = [];
79
- }
80
- this.isValid = message ? false : null;
427
+ this.update(() => {
428
+ if (message) {
429
+ this._errors = [message];
430
+ } else {
431
+ this._errors = [];
432
+ }
433
+ this.isValid = message ? false : null;
434
+ });
81
435
  }
82
436
  get valueClear() {
83
437
  return this._valueClear;
84
438
  }
439
+ get isDirty() {
440
+ return !equalValue(this._valueClear, this._baseline);
441
+ }
442
+ commitBaseline() {
443
+ return this.update(() => {
444
+ this._baseline = copyValue(this._valueClear);
445
+ });
446
+ }
85
447
  // ========== Validation ==========
86
448
  isRequired(form) {
87
449
  let isRequired;
@@ -93,41 +455,45 @@ var Field = class {
93
455
  return isRequired;
94
456
  }
95
457
  checkValid(form) {
96
- this._errors = [];
97
- if (this.isEmpty) {
98
- if (this.isRequired(form)) {
99
- this._errors.push(this.options.messages?.isRequired ?? "\u041F\u043E\u043B\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0434\u043B\u044F \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F");
100
- return false;
458
+ return this.update(() => {
459
+ this._errors = [];
460
+ if (this.isEmpty) {
461
+ if (this.isRequired(form)) {
462
+ this._errors.push(this.options.messages?.isRequired ?? "\u041F\u043E\u043B\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0434\u043B\u044F \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F");
463
+ return false;
464
+ }
465
+ return true;
101
466
  }
102
- return true;
103
- }
104
- if (this._valueClear === null) {
105
- this._errors.push(
106
- this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.fieldType.getTypeName()}`
107
- );
108
- return false;
109
- }
110
- const typeErrors = this.fieldType.validate(this._valueClear, form);
111
- if (typeErrors.length > 0) {
112
- this._errors.push(...typeErrors);
113
- return false;
114
- }
115
- if (this.options.validate) {
116
- const result = this.options.validate(this._valueClear, form);
117
- if (result === false) {
118
- this._errors.push(this.options.messages?.validate ?? "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u043E \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u044E");
467
+ if (this._valueClear === null) {
468
+ this._errors.push(
469
+ this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.fieldType.getTypeName()}`
470
+ );
119
471
  return false;
120
472
  }
121
- if (typeof result === "string") {
122
- this._errors.push(result);
473
+ const typeErrors = this.fieldType.validate(this._valueClear, form);
474
+ if (typeErrors.length > 0) {
475
+ this._errors.push(...typeErrors);
123
476
  return false;
124
477
  }
125
- }
126
- return true;
478
+ if (this.options.validate) {
479
+ const result = this.options.validate(this._valueClear, form);
480
+ if (result === false) {
481
+ this._errors.push(this.options.messages?.validate ?? "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u043E \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u044E");
482
+ return false;
483
+ }
484
+ if (typeof result === "string") {
485
+ this._errors.push(result);
486
+ return false;
487
+ }
488
+ }
489
+ return true;
490
+ });
127
491
  }
128
492
  checkAndSetValid(form) {
129
- this.isValid = this.checkValid(form);
130
- return this.isValid;
493
+ return this.update(() => {
494
+ this.isValid = this.checkValid(form);
495
+ return this.isValid;
496
+ });
131
497
  }
132
498
  get isEmpty() {
133
499
  let isEmpty = false;
@@ -138,15 +504,17 @@ var Field = class {
138
504
  }
139
505
  // ========== Helper methods ==========
140
506
  reset() {
141
- if (this.defaultValue !== null && this.defaultValue !== void 0) {
142
- this.assignRawValue(this.defaultValue);
143
- } else {
144
- this._rawValue = null;
145
- this._valueClear = null;
146
- }
147
- this._errors = [];
148
- this.isValid = null;
149
- this.isTouched = false;
507
+ return this.update(() => {
508
+ if (this.defaultValue !== null && this.defaultValue !== void 0) {
509
+ this.assignRawValue(this.defaultValue);
510
+ } else {
511
+ this._rawValue = null;
512
+ this._valueClear = null;
513
+ }
514
+ this._errors = [];
515
+ this.isValid = null;
516
+ this.isTouched = false;
517
+ });
150
518
  }
151
519
  };
152
520
 
@@ -156,28 +524,67 @@ function isValidArrayIndex(index, length) {
156
524
  }
157
525
 
158
526
  // src/core/fields/ArrayField.ts
159
- var ArrayField = class {
527
+ var ArrayField = class extends ObservableNode {
160
528
  constructor(factory, options = {}, initial = []) {
529
+ super();
161
530
  this.factory = factory;
162
531
  this._items = [];
163
532
  this._errors = [];
533
+ this._baseline = [];
164
534
  this._hasValidationResult = false;
165
- this.isTouched = false;
535
+ this._isTouched = false;
166
536
  this.options = {
167
537
  isRequired: true,
168
538
  messages: {},
169
539
  ...options
170
540
  };
171
541
  this._items = initial.map((value) => this.factory(value));
172
- this.checkValid();
542
+ this.validateAfterMutation(() => this.checkValid());
543
+ this.commitBaseline();
544
+ }
545
+ captureState() {
546
+ return {
547
+ itemsValue: snapshotValue(this.itemsValue),
548
+ items: this._items.map((item) => item.getSnapshot()),
549
+ errors: [...this._errors],
550
+ isValid: this.isValid,
551
+ isTouched: this.isTouched,
552
+ isDirty: this.isDirty
553
+ };
554
+ }
555
+ stateChildren() {
556
+ return this._items;
557
+ }
558
+ get isTouched() {
559
+ return this._isTouched;
560
+ }
561
+ set isTouched(value) {
562
+ this.update(() => {
563
+ this._isTouched = value;
564
+ });
565
+ }
566
+ get isDirty() {
567
+ return !equalValue(this.itemsValue, this._baseline) || this._items.some((child) => child.isDirty);
568
+ }
569
+ commitBaseline() {
570
+ return this.update(() => {
571
+ for (const child of this._items) child.commitBaseline();
572
+ this._baseline = copyValue(this.itemsValue);
573
+ });
173
574
  }
174
575
  // ===== value =====
175
576
  set itemsValue(items) {
176
- this.clear();
177
- for (const item of items) {
178
- this.add(item, false);
179
- }
180
- this.checkValid();
577
+ this.setItemsValue(items);
578
+ }
579
+ setItemsValue(items, validate = true) {
580
+ return this.update(() => {
581
+ this._items = [];
582
+ this.error = null;
583
+ for (const item of items) {
584
+ this.add(item, false);
585
+ }
586
+ if (validate) this.validateAfterMutation(() => this.checkValid());
587
+ });
181
588
  }
182
589
  get itemsValue() {
183
590
  return this._items.map((item) => item.valueClear);
@@ -187,20 +594,26 @@ var ArrayField = class {
187
594
  }
188
595
  // ===== actions =====
189
596
  add(value, force_check = true) {
190
- const field = this.factory(value);
191
- this.isTouched = true;
192
- this._items.push(field);
193
- if (force_check) this.checkValid();
194
- return field;
597
+ return this.update(() => {
598
+ const field = this.factory(value);
599
+ this.isTouched = true;
600
+ this._items.push(field);
601
+ if (force_check) this.validateAfterMutation(() => this.checkValid());
602
+ return field;
603
+ });
195
604
  }
196
605
  remove(index) {
197
- if (!isValidArrayIndex(index, this._items.length)) return;
198
- this._items.splice(index, 1);
199
- this.checkValid();
606
+ return this.update(() => {
607
+ if (!isValidArrayIndex(index, this._items.length)) return;
608
+ this._items.splice(index, 1);
609
+ this.validateAfterMutation(() => this.checkValid());
610
+ });
200
611
  }
201
612
  clear() {
202
- this._items = [];
203
- this.checkValid();
613
+ return this.update(() => {
614
+ this._items = [];
615
+ this.validateAfterMutation(() => this.checkValid());
616
+ });
204
617
  }
205
618
  // ===== validation =====
206
619
  isRequired(form) {
@@ -213,46 +626,48 @@ var ArrayField = class {
213
626
  return isRequired;
214
627
  }
215
628
  checkValid(form) {
216
- this._hasValidationResult = true;
217
- this._errors = [];
218
- if (this.isRequired(form) && this._items.length === 0) {
219
- this._errors.push(
220
- this.options.messages?.isRequired ?? "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u044D\u043B\u0435\u043C\u0435\u043D\u0442"
221
- );
222
- return false;
223
- }
224
- if (this.options.minItems != null && this._items.length < this.options.minItems) {
225
- this._errors.push(
226
- this.options.messages?.minItems ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 ${this.options.minItems}`
227
- );
228
- return false;
229
- }
230
- if (this.options.maxItems != null && this._items.length > this.options.maxItems) {
231
- this._errors.push(
232
- this.options.messages?.maxItems ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${this.options.maxItems}`
233
- );
234
- return false;
235
- }
236
- let valid = true;
237
- for (const field of this._items) {
238
- const ok = field.checkAndSetValid(form);
239
- if (!ok) valid = false;
240
- }
241
- if (this.options.validate) {
242
- const values = this._items.map((item) => item.valueClear);
243
- const result = this.options.validate(values, form);
244
- if (result === false) {
629
+ return this.update(() => {
630
+ this._hasValidationResult = true;
631
+ this._errors = [];
632
+ if (this.isRequired(form) && this._items.length === 0) {
245
633
  this._errors.push(
246
- this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
634
+ this.options.messages?.isRequired ?? "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u044D\u043B\u0435\u043C\u0435\u043D\u0442"
247
635
  );
248
636
  return false;
249
637
  }
250
- if (typeof result === "string") {
251
- this._errors.push(result);
638
+ if (this.options.minItems != null && this._items.length < this.options.minItems) {
639
+ this._errors.push(
640
+ this.options.messages?.minItems ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 ${this.options.minItems}`
641
+ );
252
642
  return false;
253
643
  }
254
- }
255
- return valid && this._errors.length === 0;
644
+ if (this.options.maxItems != null && this._items.length > this.options.maxItems) {
645
+ this._errors.push(
646
+ this.options.messages?.maxItems ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${this.options.maxItems}`
647
+ );
648
+ return false;
649
+ }
650
+ let valid = true;
651
+ for (const field of this._items) {
652
+ const ok = field.checkAndSetValid(form);
653
+ if (!ok) valid = false;
654
+ }
655
+ if (this.options.validate) {
656
+ const values = this._items.map((item) => item.valueClear);
657
+ const result = this.options.validate(values, form);
658
+ if (result === false) {
659
+ this._errors.push(
660
+ this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
661
+ );
662
+ return false;
663
+ }
664
+ if (typeof result === "string") {
665
+ this._errors.push(result);
666
+ return false;
667
+ }
668
+ }
669
+ return valid && this._errors.length === 0;
670
+ });
256
671
  }
257
672
  checkAndSetValid(form) {
258
673
  return this.checkValid(form);
@@ -266,51 +681,90 @@ var ArrayField = class {
266
681
  return this._errors[0] ?? "";
267
682
  }
268
683
  set error(message) {
269
- if (message) {
270
- this._errors = [message];
271
- this._hasValidationResult = true;
272
- } else {
273
- this._errors = [];
274
- this._hasValidationResult = false;
275
- }
684
+ this.update(() => {
685
+ if (message) {
686
+ this._errors = [message];
687
+ this._hasValidationResult = true;
688
+ } else {
689
+ this._errors = [];
690
+ this._hasValidationResult = false;
691
+ }
692
+ });
276
693
  }
277
694
  get errors() {
278
695
  return this._errors;
279
696
  }
280
697
  reset() {
281
- this._items.forEach((item) => item.reset());
282
- this._errors = [];
283
- this._hasValidationResult = false;
284
- this.isTouched = false;
698
+ return this.update(() => {
699
+ this._items.forEach((item) => item.reset());
700
+ this._errors = [];
701
+ this._hasValidationResult = false;
702
+ this.isTouched = false;
703
+ });
285
704
  }
286
705
  };
287
706
 
288
707
  // src/core/fields/ArrayFormField.ts
289
- var ArrayFormField = class {
708
+ var ArrayFormField = class extends ObservableNode {
290
709
  constructor(FormConstructor, options = {}, initial = []) {
710
+ super();
291
711
  this.FormConstructor = FormConstructor;
292
712
  this.options = options;
293
713
  this._forms = [];
294
714
  this._errors = [];
715
+ this._baseline = [];
295
716
  this._hasValidationResult = false;
296
- this.isTouched = false;
717
+ this._isTouched = false;
297
718
  this._forms = initial.map((data) => {
298
719
  const form = new this.FormConstructor();
299
720
  form.setFields(data, false);
300
721
  return form;
301
722
  });
302
- this.checkValid();
723
+ this.validateAfterMutation(() => this.checkValid());
724
+ this.commitBaseline();
725
+ }
726
+ captureState() {
727
+ return {
728
+ formsFieldsValue: snapshotValue(this.formsFieldsValue),
729
+ forms: this._forms.map((form) => form.getSnapshot()),
730
+ errors: [...this._errors],
731
+ isValid: this.isValid,
732
+ isTouched: this.isTouched,
733
+ isDirty: this.isDirty
734
+ };
735
+ }
736
+ stateChildren() {
737
+ return this._forms;
738
+ }
739
+ get isTouched() {
740
+ return this._isTouched;
741
+ }
742
+ set isTouched(value) {
743
+ this.update(() => {
744
+ this._isTouched = value;
745
+ });
746
+ }
747
+ get isDirty() {
748
+ return !equalValue(this.formsFieldsValue, this._baseline) || this._forms.some((child) => child.isDirty);
749
+ }
750
+ commitBaseline() {
751
+ return this.update(() => {
752
+ for (const child of this._forms) child.commitBaseline();
753
+ this._baseline = copyValue(this.formsFieldsValue);
754
+ });
303
755
  }
304
756
  // ===== value =====
305
757
  set forms(forms) {
306
758
  this.setForms(forms);
307
759
  }
308
760
  setForms(forms, validate = true) {
309
- this._forms = [];
310
- for (const formData of forms) {
311
- this.add(formData, false);
312
- }
313
- if (validate) this.checkValid();
761
+ return this.update(() => {
762
+ this._forms = [];
763
+ for (const formData of forms) {
764
+ this.add(formData, false);
765
+ }
766
+ if (validate) this.validateAfterMutation(() => this.checkValid());
767
+ });
314
768
  }
315
769
  get forms() {
316
770
  return this._forms;
@@ -320,29 +774,38 @@ var ArrayFormField = class {
320
774
  }
321
775
  // ===== actions =====
322
776
  add(data, force_check = true) {
323
- const form = new this.FormConstructor();
324
- if (data !== void 0 && data !== null) {
325
- form.setFields(data, false);
326
- }
327
- this.isTouched = true;
328
- this._forms.push(form);
329
- if (force_check) this.checkValid();
330
- return form;
777
+ return this.update(() => {
778
+ const form = new this.FormConstructor();
779
+ if (data !== void 0 && data !== null) {
780
+ form.setFields(data, false);
781
+ }
782
+ form.commitBaseline();
783
+ this.isTouched = true;
784
+ this._forms.push(form);
785
+ if (force_check) this.validateAfterMutation(() => this.checkValid());
786
+ return form;
787
+ });
331
788
  }
332
789
  remove(index) {
333
- if (!isValidArrayIndex(index, this._forms.length)) return;
334
- this._forms.splice(index, 1);
335
- this.checkValid();
790
+ return this.update(() => {
791
+ if (!isValidArrayIndex(index, this._forms.length)) return;
792
+ this._forms.splice(index, 1);
793
+ this.validateAfterMutation(() => this.checkValid());
794
+ });
336
795
  }
337
796
  clear() {
338
- this._forms = [];
339
- this.checkValid();
797
+ return this.update(() => {
798
+ this._forms = [];
799
+ this.validateAfterMutation(() => this.checkValid());
800
+ });
340
801
  }
341
802
  reset() {
342
- this._forms.forEach((form) => form.reset());
343
- this._errors = [];
344
- this._hasValidationResult = false;
345
- this.isTouched = false;
803
+ return this.update(() => {
804
+ this._forms.forEach((form) => form.reset());
805
+ this._errors = [];
806
+ this._hasValidationResult = false;
807
+ this.isTouched = false;
808
+ });
346
809
  }
347
810
  // ===== validation =====
348
811
  isRequired(form) {
@@ -355,45 +818,47 @@ var ArrayFormField = class {
355
818
  return isRequired;
356
819
  }
357
820
  checkValid(form) {
358
- this._hasValidationResult = true;
359
- this._errors = [];
360
- if (this.isRequired(form) && this._forms.length === 0) {
361
- this._errors.push(
362
- this.options.messages?.isRequired ?? "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u044D\u043B\u0435\u043C\u0435\u043D\u0442"
363
- );
364
- return false;
365
- }
366
- if (this.options.minForms != null && this._forms.length < this.options.minForms) {
367
- this._errors.push(
368
- this.options.messages?.minForms ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 ${this.options.minForms}`
369
- );
370
- return false;
371
- }
372
- if (this.options.maxForms != null && this._forms.length > this.options.maxForms) {
373
- this._errors.push(
374
- this.options.messages?.maxForms ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${this.options.maxForms}`
375
- );
376
- return false;
377
- }
378
- let valid = true;
379
- for (const nestedForm of this._forms) {
380
- const ok = nestedForm.checkValid(nestedForm);
381
- if (!ok) valid = false;
382
- }
383
- if (this.options.validate) {
384
- const result = this.options.validate(this._forms, form);
385
- if (result === false) {
821
+ return this.update(() => {
822
+ this._hasValidationResult = true;
823
+ this._errors = [];
824
+ if (this.isRequired(form) && this._forms.length === 0) {
386
825
  this._errors.push(
387
- this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
826
+ this.options.messages?.isRequired ?? "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u044D\u043B\u0435\u043C\u0435\u043D\u0442"
388
827
  );
389
828
  return false;
390
829
  }
391
- if (typeof result === "string") {
392
- this._errors.push(result);
830
+ if (this.options.minForms != null && this._forms.length < this.options.minForms) {
831
+ this._errors.push(
832
+ this.options.messages?.minForms ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 ${this.options.minForms}`
833
+ );
393
834
  return false;
394
835
  }
395
- }
396
- return valid && this._errors.length === 0;
836
+ if (this.options.maxForms != null && this._forms.length > this.options.maxForms) {
837
+ this._errors.push(
838
+ this.options.messages?.maxForms ?? `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${this.options.maxForms}`
839
+ );
840
+ return false;
841
+ }
842
+ let valid = true;
843
+ for (const nestedForm of this._forms) {
844
+ const ok = nestedForm.checkValid(nestedForm);
845
+ if (!ok) valid = false;
846
+ }
847
+ if (this.options.validate) {
848
+ const result = this.options.validate(this._forms, form);
849
+ if (result === false) {
850
+ this._errors.push(
851
+ this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
852
+ );
853
+ return false;
854
+ }
855
+ if (typeof result === "string") {
856
+ this._errors.push(result);
857
+ return false;
858
+ }
859
+ }
860
+ return valid && this._errors.length === 0;
861
+ });
397
862
  }
398
863
  checkAndSetValid(form) {
399
864
  return this.checkValid(form);
@@ -410,13 +875,15 @@ var ArrayFormField = class {
410
875
  return this._errors[0] ?? "";
411
876
  }
412
877
  set error(message) {
413
- if (message) {
414
- this._errors = [message];
415
- this._hasValidationResult = true;
416
- } else {
417
- this._errors = [];
418
- this._hasValidationResult = false;
419
- }
878
+ this.update(() => {
879
+ if (message) {
880
+ this._errors = [message];
881
+ this._hasValidationResult = true;
882
+ } else {
883
+ this._errors = [];
884
+ this._hasValidationResult = false;
885
+ }
886
+ });
420
887
  }
421
888
  };
422
889
 
@@ -671,11 +1138,29 @@ var ErrorsSchema = class _ErrorsSchema {
671
1138
  var DefaultErrorsSchema = new ErrorsSchema().fallback((key) => key);
672
1139
 
673
1140
  // src/core/Form.ts
674
- var Form = class {
1141
+ var Form = class extends ObservableNode {
675
1142
  constructor() {
1143
+ super(...arguments);
676
1144
  this._globalErrors = {};
677
1145
  this.errorsShema = DefaultErrorsSchema;
678
1146
  }
1147
+ captureState() {
1148
+ return {
1149
+ fieldsValue: snapshotValue(this.fieldsValue),
1150
+ allErrors: this.allErrors,
1151
+ globalErrors: this.globalErrors,
1152
+ fields: Object.fromEntries(Object.entries(this._fields).map(([key, field]) => [key, field.getSnapshot()])),
1153
+ isValid: this.isValid,
1154
+ isTouched: this.isTouched,
1155
+ isDirty: this.isDirty
1156
+ };
1157
+ }
1158
+ stateChildren() {
1159
+ return Object.values(this._fields);
1160
+ }
1161
+ batch(callback) {
1162
+ return this.batchUpdate(callback, () => this.checkValid());
1163
+ }
679
1164
  get _fields() {
680
1165
  const fields = {};
681
1166
  for (const key of Object.keys(this)) {
@@ -687,15 +1172,17 @@ var Form = class {
687
1172
  return fields;
688
1173
  }
689
1174
  checkValid(form) {
690
- let isFormValid = true;
691
- const fields = this._fields;
692
- for (const key of Object.keys(fields)) {
693
- const field = fields[key];
694
- if (!field.checkAndSetValid(form ?? this)) {
695
- isFormValid = false;
1175
+ return this.update(() => {
1176
+ let isFormValid = true;
1177
+ const fields = this._fields;
1178
+ for (const key of Object.keys(fields)) {
1179
+ const field = fields[key];
1180
+ if (!field.checkAndSetValid(form ?? this)) {
1181
+ isFormValid = false;
1182
+ }
696
1183
  }
697
- }
698
- return isFormValid && Object.keys(this._globalErrors).length === 0;
1184
+ return isFormValid && Object.keys(this._globalErrors).length === 0;
1185
+ });
699
1186
  }
700
1187
  get fieldsValue() {
701
1188
  const data = {};
@@ -715,17 +1202,19 @@ var Form = class {
715
1202
  this.setFields(data);
716
1203
  }
717
1204
  setFields(data, validate = true) {
718
- const fields = this._fields;
719
- for (const key of Object.keys(data)) {
720
- const field = fields[key];
721
- if (field) {
722
- if (field instanceof Field) field.setRawValue(data[key], false);
723
- else if (field instanceof ArrayField) field.itemsValue = data[key];
724
- else if (field instanceof ArrayFormField) field.setForms(data[key], false);
725
- else console.error(`Invalid field "${key}" type`);
1205
+ return this.update(() => {
1206
+ const fields = this._fields;
1207
+ for (const key of Object.keys(data)) {
1208
+ const field = fields[key];
1209
+ if (field) {
1210
+ if (field instanceof Field) field.setRawValue(data[key], false);
1211
+ else if (field instanceof ArrayField) field.setItemsValue(data[key], false);
1212
+ else if (field instanceof ArrayFormField) field.setForms(data[key], false);
1213
+ else console.error(`Invalid field "${key}" type`);
1214
+ }
726
1215
  }
727
- }
728
- if (validate) this.checkValid();
1216
+ if (validate) this.validateAfterMutation(() => this.checkValid());
1217
+ });
729
1218
  }
730
1219
  get allErrors() {
731
1220
  const errors = {};
@@ -740,7 +1229,7 @@ var Form = class {
740
1229
  } else {
741
1230
  const fieldErrors = field.errors;
742
1231
  if (fieldErrors.length > 0) {
743
- errors[key] = fieldErrors;
1232
+ errors[key] = [...fieldErrors];
744
1233
  }
745
1234
  }
746
1235
  }
@@ -773,26 +1262,30 @@ var Form = class {
773
1262
  return { ...this._globalErrors };
774
1263
  }
775
1264
  set errors(errors) {
776
- this.clearErrorsRecursively();
777
- const fields = this._fields;
778
- const parsedErrors = this.errorsShema.parseErrors(errors);
779
- for (const [fieldName, errorMessage] of Object.entries(parsedErrors)) {
780
- this.applyErrorToField(fieldName, errorMessage, fields, this);
781
- }
1265
+ this.update(() => {
1266
+ this.clearErrorsRecursively();
1267
+ const fields = this._fields;
1268
+ const parsedErrors = this.errorsShema.parseErrors(errors);
1269
+ for (const [fieldName, errorMessage] of Object.entries(parsedErrors)) {
1270
+ this.applyErrorToField(fieldName, errorMessage, fields, this);
1271
+ }
1272
+ });
782
1273
  }
783
1274
  clearErrorsRecursively() {
784
- this._globalErrors = {};
785
- for (const field of Object.values(this._fields)) {
786
- if (field instanceof Field) {
787
- field.error = null;
788
- } else if (field instanceof ArrayField) {
789
- field.error = null;
790
- for (const item of field.items) item.error = null;
791
- } else if (field instanceof ArrayFormField) {
792
- field.error = null;
793
- for (const nestedForm of field.forms) nestedForm.clearErrorsRecursively();
1275
+ this.update(() => {
1276
+ this._globalErrors = {};
1277
+ for (const field of Object.values(this._fields)) {
1278
+ if (field instanceof Field) {
1279
+ field.error = null;
1280
+ } else if (field instanceof ArrayField) {
1281
+ field.error = null;
1282
+ for (const item of field.items) item.error = null;
1283
+ } else if (field instanceof ArrayFormField) {
1284
+ field.error = null;
1285
+ for (const nestedForm of field.forms) nestedForm.clearErrorsRecursively();
1286
+ }
794
1287
  }
795
- }
1288
+ });
796
1289
  }
797
1290
  /**
798
1291
  * Apply an error to the corresponding field.
@@ -844,17 +1337,31 @@ var Form = class {
844
1337
  }
845
1338
  }
846
1339
  setGlobalError(key, message) {
847
- this._globalErrors[key] = message;
1340
+ return this.update(() => {
1341
+ this._globalErrors[key] = message;
1342
+ });
848
1343
  }
849
1344
  clearGlobalErrors() {
850
- this._globalErrors = {};
1345
+ return this.update(() => {
1346
+ this._globalErrors = {};
1347
+ });
851
1348
  }
852
1349
  reset() {
853
- const fields = this._fields;
854
- for (const key of Object.keys(fields)) {
855
- fields[key].reset();
856
- }
857
- this._globalErrors = {};
1350
+ return this.update(() => {
1351
+ const fields = this._fields;
1352
+ for (const key of Object.keys(fields)) {
1353
+ fields[key].reset();
1354
+ }
1355
+ this._globalErrors = {};
1356
+ });
1357
+ }
1358
+ get isDirty() {
1359
+ return Object.values(this._fields).some((field) => field.isDirty);
1360
+ }
1361
+ commitBaseline() {
1362
+ return this.update(() => {
1363
+ for (const field of Object.values(this._fields)) field.commitBaseline();
1364
+ });
858
1365
  }
859
1366
  get isTouched() {
860
1367
  const fields = this._fields;
@@ -1245,14 +1752,6 @@ var DecimalType = class extends FieldType {
1245
1752
  const parts = normalizedValue.split(".");
1246
1753
  const intPart = parts[0];
1247
1754
  const decPart = parts.length > 1 ? parts[1] : "";
1248
- if (normalizedValue.endsWith(".")) {
1249
- if (intPart.length > maxIntDigits) {
1250
- errors.push(
1251
- this.options.messages?.maxDigits ?? `\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0437\u043D\u0430\u043A\u043E\u0432 \u043F\u0435\u0440\u0435\u0434 \u0437\u0430\u043F\u044F\u0442\u043E\u0439 ${maxIntDigits}`
1252
- );
1253
- }
1254
- return errors;
1255
- }
1256
1755
  if (intPart.length === 0) {
1257
1756
  errors.push(this.options.messages?.invalid ?? "\u041F\u0440\u0438\u043C\u0435\u0440: 1.00");
1258
1757
  return errors;