adaptform 2.0.4 → 2.1.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
@@ -6,7 +6,7 @@ var Field = class {
6
6
  this._errors = [];
7
7
  this.isValid = null;
8
8
  this.isTouched = false;
9
- this.filed_type = new TypeConstructor(options ?? {});
9
+ this.fieldType = new TypeConstructor(options ?? {});
10
10
  this.defaultValue = defaultValue ?? null;
11
11
  for (const plugin of this.options.plugins ?? []) {
12
12
  plugin.init?.(this);
@@ -18,24 +18,38 @@ var Field = class {
18
18
  this._valueClear = null;
19
19
  this.isValid = null;
20
20
  }
21
+ this.isTouched = false;
21
22
  }
22
- // ========== Геттеры/сеттеры ==========
23
+ // ========== Getters and setters ==========
23
24
  get plugins() {
24
25
  return [...this.options.plugins ?? []].sort((a, b) => {
25
26
  return (a.pluginOptions?.priority ?? 0) - (b.pluginOptions?.priority ?? 0);
26
27
  });
27
28
  }
28
29
  get options() {
29
- return this.filed_type.options;
30
+ return this.fieldType.options;
30
31
  }
31
32
  set options(options) {
32
- let cur_options = this.filed_type.options;
33
- this.filed_type.options = Object.assign(cur_options, options);
33
+ const currentOptions = this.fieldType.options;
34
+ this.fieldType.options = Object.assign(currentOptions, options);
34
35
  }
35
36
  get rawValue() {
36
37
  return this._rawValue;
37
38
  }
38
39
  set rawValue(input) {
40
+ this.setRawValue(input);
41
+ }
42
+ 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
+ }
51
+ }
52
+ assignRawValue(input) {
39
53
  let rawValue = input;
40
54
  for (const plugin of this.plugins) {
41
55
  if (plugin.toRawValue) {
@@ -49,10 +63,7 @@ var Field = class {
49
63
  }
50
64
  }
51
65
  this._rawValue = rawValue;
52
- this._valueClear = this.filed_type.cast(valueClear);
53
- this._errors = [];
54
- this.isTouched = true;
55
- this.checkAndSetValid();
66
+ this._valueClear = this.fieldType.cast(valueClear);
56
67
  }
57
68
  get errors() {
58
69
  return this._errors;
@@ -71,7 +82,7 @@ var Field = class {
71
82
  get valueClear() {
72
83
  return this._valueClear;
73
84
  }
74
- // ========== Валидация ==========
85
+ // ========== Validation ==========
75
86
  isRequired(form) {
76
87
  let isRequired;
77
88
  if (typeof this.options.isRequired === "function") {
@@ -92,11 +103,11 @@ var Field = class {
92
103
  }
93
104
  if (this._valueClear === null) {
94
105
  this._errors.push(
95
- this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.filed_type.getTypeName()}`
106
+ this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.fieldType.getTypeName()}`
96
107
  );
97
108
  return false;
98
109
  }
99
- const typeErrors = this.filed_type.validate(this._valueClear, form);
110
+ const typeErrors = this.fieldType.validate(this._valueClear, form);
100
111
  if (typeErrors.length > 0) {
101
112
  this._errors.push(...typeErrors);
102
113
  return false;
@@ -121,55 +132,61 @@ var Field = class {
121
132
  get isEmpty() {
122
133
  let isEmpty = false;
123
134
  if (this._rawValue === null || this._rawValue === void 0) isEmpty = true;
124
- else if (this._rawValue === "") isEmpty = true;
135
+ else if (typeof this._rawValue === "string" && this._rawValue.trim() === "") isEmpty = true;
125
136
  else if (Array.isArray(this._rawValue) && this._rawValue.length === 0) isEmpty = true;
126
137
  return isEmpty;
127
138
  }
128
- // ========== Вспомогательные методы ==========
139
+ // ========== Helper methods ==========
129
140
  reset() {
130
141
  if (this.defaultValue !== null && this.defaultValue !== void 0) {
131
- this.rawValue = this.defaultValue;
132
- this.isValid = true;
142
+ this.assignRawValue(this.defaultValue);
133
143
  } else {
134
144
  this._rawValue = null;
135
145
  this._valueClear = null;
136
- this.isValid = null;
137
146
  }
138
147
  this._errors = [];
148
+ this.isValid = null;
139
149
  this.isTouched = false;
140
150
  }
141
151
  };
142
152
 
153
+ // src/core/fields/arrayUtils.ts
154
+ function isValidArrayIndex(index, length) {
155
+ return Number.isInteger(index) && index >= 0 && index < length;
156
+ }
157
+
143
158
  // src/core/fields/ArrayField.ts
144
159
  var ArrayField = class {
145
160
  constructor(factory, options = {}, initial = []) {
146
161
  this.factory = factory;
147
162
  this._items = [];
148
163
  this._errors = [];
164
+ this._hasValidationResult = false;
149
165
  this.isTouched = false;
150
166
  this.options = {
151
167
  isRequired: true,
152
168
  messages: {},
153
169
  ...options
154
170
  };
155
- this._items = initial.map((v) => this.factory(v));
171
+ this._items = initial.map((value) => this.factory(value));
172
+ this.checkValid();
156
173
  }
157
174
  // ===== value =====
158
175
  set itemsValue(items) {
159
176
  this.clear();
160
- for (const i of items) {
161
- this.add(i, false);
177
+ for (const item of items) {
178
+ this.add(item, false);
162
179
  }
163
180
  this.checkValid();
164
181
  }
165
182
  get itemsValue() {
166
- return this._items.map((i) => i.valueClear);
183
+ return this._items.map((item) => item.valueClear);
167
184
  }
168
185
  get items() {
169
186
  return this._items;
170
187
  }
171
188
  // ===== actions =====
172
- add(value, force_check) {
189
+ add(value, force_check = true) {
173
190
  const field = this.factory(value);
174
191
  this.isTouched = true;
175
192
  this._items.push(field);
@@ -177,6 +194,7 @@ var ArrayField = class {
177
194
  return field;
178
195
  }
179
196
  remove(index) {
197
+ if (!isValidArrayIndex(index, this._items.length)) return;
180
198
  this._items.splice(index, 1);
181
199
  this.checkValid();
182
200
  }
@@ -195,6 +213,7 @@ var ArrayField = class {
195
213
  return isRequired;
196
214
  }
197
215
  checkValid(form) {
216
+ this._hasValidationResult = true;
198
217
  this._errors = [];
199
218
  if (this.isRequired(form) && this._items.length === 0) {
200
219
  this._errors.push(
@@ -220,7 +239,8 @@ var ArrayField = class {
220
239
  if (!ok) valid = false;
221
240
  }
222
241
  if (this.options.validate) {
223
- const result = this.options.validate(this._items, form);
242
+ const values = this._items.map((item) => item.valueClear);
243
+ const result = this.options.validate(values, form);
224
244
  if (result === false) {
225
245
  this._errors.push(
226
246
  this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
@@ -238,7 +258,9 @@ var ArrayField = class {
238
258
  return this.checkValid(form);
239
259
  }
240
260
  get isValid() {
241
- return this._errors.length === 0 && this._items.every((i) => i.isValid === true);
261
+ if (!this._hasValidationResult) return false;
262
+ if (this._errors.length > 0) return false;
263
+ return this._items.every((item) => item.isValid === true);
242
264
  }
243
265
  get error() {
244
266
  return this._errors[0] ?? "";
@@ -246,16 +268,19 @@ var ArrayField = class {
246
268
  set error(message) {
247
269
  if (message) {
248
270
  this._errors = [message];
271
+ this._hasValidationResult = true;
249
272
  } else {
250
273
  this._errors = [];
274
+ this._hasValidationResult = false;
251
275
  }
252
276
  }
253
277
  get errors() {
254
278
  return this._errors;
255
279
  }
256
280
  reset() {
257
- this._items.forEach((i) => i.reset());
281
+ this._items.forEach((item) => item.reset());
258
282
  this._errors = [];
283
+ this._hasValidationResult = false;
259
284
  this.isTouched = false;
260
285
  }
261
286
  };
@@ -267,37 +292,45 @@ var ArrayFormField = class {
267
292
  this.options = options;
268
293
  this._forms = [];
269
294
  this._errors = [];
295
+ this._hasValidationResult = false;
270
296
  this.isTouched = false;
271
297
  this._forms = initial.map((data) => {
272
298
  const form = new this.FormConstructor();
273
- form.fields = data;
299
+ form.setFields(data, false);
274
300
  return form;
275
301
  });
302
+ this.checkValid();
276
303
  }
277
304
  // ===== value =====
278
305
  set forms(forms) {
279
- this.clear();
280
- for (const f of forms) {
281
- this.add(f, false);
306
+ this.setForms(forms);
307
+ }
308
+ setForms(forms, validate = true) {
309
+ this._forms = [];
310
+ for (const formData of forms) {
311
+ this.add(formData, false);
282
312
  }
283
- this.checkValid();
313
+ if (validate) this.checkValid();
284
314
  }
285
315
  get forms() {
286
316
  return this._forms;
287
317
  }
288
318
  get formsFieldsValue() {
289
- return this._forms.map((i) => i.fieldsValue);
319
+ return this._forms.map((form) => form.fieldsValue);
290
320
  }
291
321
  // ===== actions =====
292
- add(data, force_check) {
322
+ add(data, force_check = true) {
293
323
  const form = new this.FormConstructor();
294
- if (data) form.fields = data;
324
+ if (data !== void 0 && data !== null) {
325
+ form.setFields(data, false);
326
+ }
295
327
  this.isTouched = true;
296
328
  this._forms.push(form);
297
329
  if (force_check) this.checkValid();
298
330
  return form;
299
331
  }
300
332
  remove(index) {
333
+ if (!isValidArrayIndex(index, this._forms.length)) return;
301
334
  this._forms.splice(index, 1);
302
335
  this.checkValid();
303
336
  }
@@ -306,8 +339,9 @@ var ArrayFormField = class {
306
339
  this.checkValid();
307
340
  }
308
341
  reset() {
309
- this._forms.forEach((f) => f.reset());
342
+ this._forms.forEach((form) => form.reset());
310
343
  this._errors = [];
344
+ this._hasValidationResult = false;
311
345
  this.isTouched = false;
312
346
  }
313
347
  // ===== validation =====
@@ -321,8 +355,9 @@ var ArrayFormField = class {
321
355
  return isRequired;
322
356
  }
323
357
  checkValid(form) {
358
+ this._hasValidationResult = true;
324
359
  this._errors = [];
325
- if (this.isRequired(form) && this.forms.length === 0) {
360
+ if (this.isRequired(form) && this._forms.length === 0) {
326
361
  this._errors.push(
327
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"
328
363
  );
@@ -341,12 +376,12 @@ var ArrayFormField = class {
341
376
  return false;
342
377
  }
343
378
  let valid = true;
344
- for (const form2 of this.forms) {
345
- const ok = form2.checkValid(form2);
379
+ for (const nestedForm of this._forms) {
380
+ const ok = nestedForm.checkValid(nestedForm);
346
381
  if (!ok) valid = false;
347
382
  }
348
383
  if (this.options.validate) {
349
- const result = this.options.validate(this.forms, form);
384
+ const result = this.options.validate(this._forms, form);
350
385
  if (result === false) {
351
386
  this._errors.push(
352
387
  this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
@@ -364,7 +399,9 @@ var ArrayFormField = class {
364
399
  return this.checkValid(form);
365
400
  }
366
401
  get isValid() {
367
- return this._errors.length === 0 && this._forms.every((i) => i.checkValid() === true);
402
+ if (!this._hasValidationResult) return false;
403
+ if (this._errors.length > 0) return false;
404
+ return this._forms.every((form) => form.isValid);
368
405
  }
369
406
  get errors() {
370
407
  return this._errors;
@@ -375,16 +412,269 @@ var ArrayFormField = class {
375
412
  set error(message) {
376
413
  if (message) {
377
414
  this._errors = [message];
415
+ this._hasValidationResult = true;
378
416
  } else {
379
417
  this._errors = [];
418
+ this._hasValidationResult = false;
380
419
  }
381
420
  }
382
421
  };
383
422
 
423
+ // src/core/ErrorsShema.ts
424
+ var ErrorsSchema = class _ErrorsSchema {
425
+ constructor() {
426
+ this._staticRules = /* @__PURE__ */ new Map();
427
+ this._dynamicRules = [];
428
+ this._valueTransformers = [];
429
+ }
430
+ /**
431
+ * Add a static rule that matches an exact error key.
432
+ * @param errorKey - Exact error key returned by the server.
433
+ * @param fieldName - Form field name or a function that resolves it dynamically.
434
+ */
435
+ staticKey(errorKey, fieldName) {
436
+ this._staticRules.set(errorKey, fieldName);
437
+ return this;
438
+ }
439
+ /**
440
+ * Add a dynamic rule using a regular expression.
441
+ * @param pattern - Regular expression used to match an error key.
442
+ * @param mapping - Field name or function that derives it from the match.
443
+ * @param options - Additional options, such as rule priority.
444
+ */
445
+ dynamicKey(pattern, mapping, options) {
446
+ this._dynamicRules.push({
447
+ pattern,
448
+ mapping,
449
+ priority: options?.priority ?? 0
450
+ });
451
+ this._dynamicRules.sort((a, b) => b.priority - a.priority);
452
+ return this;
453
+ }
454
+ /**
455
+ * Set a handler for errors that do not match any rule.
456
+ */
457
+ fallback(handler) {
458
+ this._fallbackHandler = handler;
459
+ return this;
460
+ }
461
+ /**
462
+ * Add a transformer for error values.
463
+ * Useful for normalizing messages, such as server responses containing arrays.
464
+ */
465
+ transformValue(transformer) {
466
+ this._valueTransformers.push(transformer);
467
+ return this;
468
+ }
469
+ /**
470
+ * Create a schema from a simple mapping object.
471
+ */
472
+ static fromObject(mapping) {
473
+ const schema = new _ErrorsSchema();
474
+ for (const [errorKey, fieldName] of Object.entries(mapping)) {
475
+ schema.staticKey(errorKey, fieldName);
476
+ }
477
+ return schema;
478
+ }
479
+ /**
480
+ * Create a schema for an array of similarly structured forms.
481
+ */
482
+ static forArray(itemSchema, arrayFieldName, indexExtractor) {
483
+ const schema = new _ErrorsSchema();
484
+ for (const transformer of itemSchema._valueTransformers) {
485
+ schema.transformValue(transformer);
486
+ }
487
+ const escapedArrayFieldName = arrayFieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
488
+ const indexPattern = indexExtractor ? "(.+?)" : "(?:\\[(\\d+)\\]|\\.(\\d+))";
489
+ schema.dynamicKey(
490
+ new RegExp(`^${escapedArrayFieldName}${indexPattern}\\.(.+)$`),
491
+ (match, value) => {
492
+ const sourceKey = match[0];
493
+ const index = indexExtractor ? indexExtractor(sourceKey) : Number(match[1] ?? match[2]);
494
+ if (!Number.isInteger(index) || index < 0) {
495
+ return "";
496
+ }
497
+ const fieldPath = match[indexExtractor ? 2 : 3];
498
+ const itemMapping = itemSchema.findMapping(fieldPath, value);
499
+ const mappedFieldPath = itemMapping ? typeof itemMapping === "function" ? itemMapping(value) : itemMapping : fieldPath;
500
+ return `${arrayFieldName}.${index}.${mappedFieldPath}`;
501
+ },
502
+ { priority: 100 }
503
+ );
504
+ return schema;
505
+ }
506
+ /**
507
+ * Parse errors while applying configured value transformers.
508
+ * Supports nested structures using dot notation.
509
+ */
510
+ parseErrors(errors, prefix = "") {
511
+ const result = {};
512
+ for (const [key, value] of Object.entries(errors)) {
513
+ const fullKey = prefix ? `${prefix}.${key}` : key;
514
+ if (this.isNestedObject(value)) {
515
+ const nestedErrors = this.parseErrors(value, fullKey);
516
+ Object.assign(result, nestedErrors);
517
+ continue;
518
+ }
519
+ if (value === null || value === void 0) {
520
+ continue;
521
+ }
522
+ let transformedValue = value;
523
+ for (const transformer of this._valueTransformers) {
524
+ transformedValue = transformer(transformedValue);
525
+ }
526
+ const mapping = this.findMapping(fullKey, transformedValue);
527
+ if (mapping) {
528
+ if (typeof mapping === "function") {
529
+ const fieldName = mapping(transformedValue);
530
+ if (fieldName) {
531
+ result[fieldName] = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
532
+ }
533
+ } else {
534
+ result[mapping] = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
535
+ }
536
+ }
537
+ }
538
+ return result;
539
+ }
540
+ /**
541
+ * Parse server errors and return detailed metadata.
542
+ */
543
+ parseErrorsDetailed(errors, prefix = "") {
544
+ const result = {};
545
+ for (const [key, value] of Object.entries(errors)) {
546
+ const fullKey = prefix ? `${prefix}.${key}` : key;
547
+ if (this.isNestedObject(value)) {
548
+ const nestedErrors = this.parseErrorsDetailed(value, fullKey);
549
+ Object.assign(result, nestedErrors);
550
+ continue;
551
+ }
552
+ if (value === null || value === void 0) {
553
+ continue;
554
+ }
555
+ let transformedValue = value;
556
+ for (const transformer of this._valueTransformers) {
557
+ transformedValue = transformer(transformedValue);
558
+ }
559
+ const matchResult = this.findMappingDetailed(fullKey, transformedValue);
560
+ if (matchResult) {
561
+ const message = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
562
+ result[matchResult.fieldName] = {
563
+ fieldName: matchResult.fieldName,
564
+ message,
565
+ sourceKey: fullKey,
566
+ matchedBy: matchResult.matchedBy
567
+ };
568
+ }
569
+ }
570
+ return result;
571
+ }
572
+ /**
573
+ * Create a schema for a nested form.
574
+ */
575
+ nest(prefix) {
576
+ const nestedSchema = new _ErrorsSchema();
577
+ for (const [key, mapping] of this._staticRules.entries()) {
578
+ nestedSchema.staticKey(`${prefix}.${key}`, mapping);
579
+ }
580
+ for (const rule of this._dynamicRules) {
581
+ const newPattern = new RegExp(`^${prefix}\\.${rule.pattern.source.replace(/^\^/, "")}`);
582
+ nestedSchema.dynamicKey(newPattern, rule.mapping, { priority: rule.priority });
583
+ }
584
+ for (const transformer of this._valueTransformers) {
585
+ nestedSchema.transformValue(transformer);
586
+ }
587
+ if (this._fallbackHandler) {
588
+ nestedSchema.fallback((key, value) => {
589
+ if (key.startsWith(`${prefix}.`)) {
590
+ return this._fallbackHandler(key.slice(prefix.length + 1), value);
591
+ }
592
+ return null;
593
+ });
594
+ }
595
+ return nestedSchema;
596
+ }
597
+ /**
598
+ * Clone the current schema.
599
+ */
600
+ clone() {
601
+ const cloned = new _ErrorsSchema();
602
+ for (const [key, mapping] of this._staticRules.entries()) {
603
+ cloned._staticRules.set(key, mapping);
604
+ }
605
+ for (const rule of this._dynamicRules) {
606
+ cloned._dynamicRules.push({ ...rule });
607
+ }
608
+ cloned._fallbackHandler = this._fallbackHandler;
609
+ cloned._valueTransformers = [...this._valueTransformers];
610
+ return cloned;
611
+ }
612
+ /**
613
+ * Merge the current schema with another schema.
614
+ */
615
+ merge(other) {
616
+ const merged = this.clone();
617
+ for (const [key, mapping] of other._staticRules.entries()) {
618
+ merged._staticRules.set(key, mapping);
619
+ }
620
+ merged._dynamicRules.push(...other._dynamicRules);
621
+ merged._dynamicRules.sort((a, b) => b.priority - a.priority);
622
+ merged._valueTransformers.push(...other._valueTransformers);
623
+ if (other._fallbackHandler) {
624
+ merged._fallbackHandler = other._fallbackHandler;
625
+ }
626
+ return merged;
627
+ }
628
+ isNestedObject(value) {
629
+ return value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
630
+ }
631
+ findMappingDetailed(key, value) {
632
+ if (this._staticRules.has(key)) {
633
+ const mapping = this._staticRules.get(key);
634
+ const fieldName = typeof mapping === "function" ? mapping(value) : mapping;
635
+ return fieldName ? { fieldName, matchedBy: "static" } : null;
636
+ }
637
+ for (const rule of this._dynamicRules) {
638
+ const match = key.match(rule.pattern);
639
+ if (match) {
640
+ const fieldName = typeof rule.mapping === "function" ? rule.mapping(match, value) : rule.mapping;
641
+ return fieldName ? { fieldName, matchedBy: "dynamic" } : null;
642
+ }
643
+ }
644
+ if (this._fallbackHandler) {
645
+ const fieldName = this._fallbackHandler(key, value);
646
+ return fieldName ? { fieldName, matchedBy: "fallback" } : null;
647
+ }
648
+ return null;
649
+ }
650
+ findMapping(key, value) {
651
+ if (this._staticRules.has(key)) {
652
+ return this._staticRules.get(key);
653
+ }
654
+ for (const rule of this._dynamicRules) {
655
+ const match = key.match(rule.pattern);
656
+ if (match) {
657
+ if (typeof rule.mapping === "function") {
658
+ const fieldName = rule.mapping(match, value);
659
+ return fieldName;
660
+ }
661
+ return rule.mapping;
662
+ }
663
+ }
664
+ if (this._fallbackHandler) {
665
+ const fieldName = this._fallbackHandler(key, value);
666
+ return fieldName;
667
+ }
668
+ return null;
669
+ }
670
+ };
671
+ var DefaultErrorsSchema = new ErrorsSchema().fallback((key) => key);
672
+
384
673
  // src/core/Form.ts
385
674
  var Form = class {
386
675
  constructor() {
387
676
  this._globalErrors = {};
677
+ this.errorsShema = DefaultErrorsSchema;
388
678
  }
389
679
  get _fields() {
390
680
  const fields = {};
@@ -405,7 +695,7 @@ var Form = class {
405
695
  isFormValid = false;
406
696
  }
407
697
  }
408
- return isFormValid;
698
+ return isFormValid && Object.keys(this._globalErrors).length === 0;
409
699
  }
410
700
  get fieldsValue() {
411
701
  const data = {};
@@ -422,24 +712,36 @@ var Form = class {
422
712
  return data;
423
713
  }
424
714
  set fields(data) {
715
+ this.setFields(data);
716
+ }
717
+ setFields(data, validate = true) {
425
718
  const fields = this._fields;
426
719
  for (const key of Object.keys(data)) {
427
720
  const field = fields[key];
428
721
  if (field) {
429
- if (field instanceof Field) field.rawValue = data[key];
722
+ if (field instanceof Field) field.setRawValue(data[key], false);
430
723
  else if (field instanceof ArrayField) field.itemsValue = data[key];
431
- else if (field instanceof ArrayFormField) field.forms = data[key];
724
+ else if (field instanceof ArrayFormField) field.setForms(data[key], false);
432
725
  else console.error(`Invalid field "${key}" type`);
433
726
  }
434
727
  }
728
+ if (validate) this.checkValid();
435
729
  }
436
730
  get allErrors() {
437
731
  const errors = {};
438
732
  const fields = this._fields;
439
733
  for (const key of Object.keys(fields)) {
440
- const fieldErrors = fields[key].errors;
441
- if (fieldErrors.length > 0) {
442
- errors[key] = fieldErrors;
734
+ const field = fields[key];
735
+ if (field instanceof ArrayFormField) {
736
+ const formErrors = this.collectArrayFormErrors(field);
737
+ if (formErrors.length > 0) {
738
+ errors[key] = formErrors;
739
+ }
740
+ } else {
741
+ const fieldErrors = field.errors;
742
+ if (fieldErrors.length > 0) {
743
+ errors[key] = fieldErrors;
744
+ }
443
745
  }
444
746
  }
445
747
  for (const key of Object.keys(this._globalErrors)) {
@@ -450,20 +752,95 @@ var Form = class {
450
752
  }
451
753
  return errors;
452
754
  }
755
+ /**
756
+ * Recursively collect all errors from an ArrayFormField.
757
+ */
758
+ collectArrayFormErrors(arrayField) {
759
+ const allErrors = [];
760
+ allErrors.push(...arrayField.errors);
761
+ for (let i = 0; i < arrayField.forms.length; i++) {
762
+ const form = arrayField.forms[i];
763
+ const formAllErrors = form.allErrors;
764
+ for (const [fieldName, fieldErrors] of Object.entries(formAllErrors)) {
765
+ for (const error of fieldErrors) {
766
+ allErrors.push(`\u0424\u043E\u0440\u043C\u0430 ${i + 1}, ${fieldName}: ${error}`);
767
+ }
768
+ }
769
+ }
770
+ return allErrors;
771
+ }
453
772
  get globalErrors() {
454
773
  return { ...this._globalErrors };
455
774
  }
456
775
  set errors(errors) {
776
+ this.clearErrorsRecursively();
457
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
+ }
782
+ }
783
+ clearErrorsRecursively() {
458
784
  this._globalErrors = {};
459
- for (const key of Object.keys(errors)) {
460
- const keys = key.split(".");
461
- const cleanKey = keys.length > 1 ? keys[1] : key;
462
- if (cleanKey in fields && fields[cleanKey]) {
463
- fields[cleanKey].error = errors[key];
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();
794
+ }
795
+ }
796
+ }
797
+ /**
798
+ * Apply an error to the corresponding field.
799
+ * Supports nested fields using dot notation.
800
+ */
801
+ applyErrorToField(fieldPath, errorMessage, fields, targetForm) {
802
+ if (!targetForm) {
803
+ console.error("applyErrorToField: targetForm is undefined");
804
+ return;
805
+ }
806
+ if (fieldPath in fields && fields[fieldPath]) {
807
+ fields[fieldPath].error = errorMessage;
808
+ return;
809
+ }
810
+ const parts = fieldPath.split(".");
811
+ const rootField = parts[0];
812
+ if (rootField in fields && fields[rootField]) {
813
+ const field = fields[rootField];
814
+ if (parts.length === 1) {
815
+ field.error = errorMessage;
816
+ } else if (parts.length > 1) {
817
+ this.applyNestedError(field, parts.slice(1), errorMessage, fieldPath, targetForm);
818
+ }
819
+ } else {
820
+ targetForm.setGlobalError(fieldPath, errorMessage);
821
+ }
822
+ }
823
+ applyNestedError(field, pathParts, errorMessage, fullPath, targetForm) {
824
+ if (!targetForm) {
825
+ console.error("applyNestedError: targetForm is undefined");
826
+ return;
827
+ }
828
+ const [currentPart, ...remainingParts] = pathParts;
829
+ if (field instanceof ArrayFormField) {
830
+ const index = parseInt(currentPart);
831
+ if (!isNaN(index) && index >= 0 && index < field.forms.length) {
832
+ const form = field.forms[index];
833
+ if (remainingParts.length > 0) {
834
+ const formFields = form["_fields"];
835
+ this.applyErrorToField(remainingParts.join("."), errorMessage, formFields, form);
836
+ } else {
837
+ form.setGlobalError(`index_${index}`, errorMessage);
838
+ }
464
839
  } else {
465
- this._globalErrors[key] = errors[key];
840
+ targetForm.setGlobalError(fullPath, errorMessage);
466
841
  }
842
+ } else {
843
+ targetForm.setGlobalError(fullPath, errorMessage);
467
844
  }
468
845
  }
469
846
  setGlobalError(key, message) {
@@ -486,7 +863,7 @@ var Form = class {
486
863
  }
487
864
  return false;
488
865
  }
489
- // Проверить, валидна ли форма
866
+ // Check whether the form is valid.
490
867
  get isValid() {
491
868
  const fields = this._fields;
492
869
  for (const key of Object.keys(fields)) {
@@ -494,32 +871,50 @@ var Form = class {
494
871
  }
495
872
  return Object.keys(this._globalErrors).length === 0;
496
873
  }
497
- // Получить все значения в виде FormData (для отправки на сервер)
874
+ // Serialize form values for multipart requests.
498
875
  toFormData() {
499
876
  const formData = new FormData();
500
877
  const data = this.fieldsValue;
501
878
  for (const [key, value] of Object.entries(data)) {
502
- if (value instanceof Date) {
879
+ if (value === null || value === void 0) continue;
880
+ if (typeof Blob !== "undefined" && value instanceof Blob) {
881
+ formData.append(key, value);
882
+ } else if (value instanceof Date) {
503
883
  formData.append(key, value.toISOString());
504
- } else if (value !== null && value !== void 0) {
884
+ } else if (typeof value === "object") {
885
+ const serialized = JSON.stringify(value);
886
+ if (serialized !== void 0) formData.append(key, serialized);
887
+ } else {
505
888
  formData.append(key, String(value));
506
889
  }
507
890
  }
508
891
  return formData;
509
892
  }
510
- // Получить все значения в виде JSON
893
+ // Get all values in JSON-compatible form.
511
894
  toJSON() {
512
- const data = this.fieldsValue;
513
895
  const json = {};
514
- for (const [key, value] of Object.entries(data)) {
515
- if (value instanceof Date) {
516
- json[key] = value.toISOString();
517
- } else {
518
- json[key] = value;
519
- }
896
+ for (const [key, value] of Object.entries(this.fieldsValue)) {
897
+ json[key] = this.serializeJsonValue(value);
520
898
  }
521
899
  return json;
522
900
  }
901
+ serializeJsonValue(value) {
902
+ if (value instanceof Date) {
903
+ return value.toISOString();
904
+ }
905
+ if (Array.isArray(value)) {
906
+ return value.map((item) => this.serializeJsonValue(item));
907
+ }
908
+ if (value !== null && typeof value === "object") {
909
+ const prototype = Object.getPrototypeOf(value);
910
+ if (prototype === Object.prototype || prototype === null) {
911
+ return Object.fromEntries(
912
+ Object.entries(value).map(([key, nestedValue]) => [key, this.serializeJsonValue(nestedValue)])
913
+ );
914
+ }
915
+ }
916
+ return value;
917
+ }
523
918
  };
524
919
 
525
920
  // src/core/plugins/FieldPlugin.ts
@@ -539,13 +934,13 @@ var MaskPlugin = class extends BasePlugin {
539
934
  this.maskParts = [];
540
935
  this.config = config;
541
936
  }
542
- init(field) {
937
+ init(_field) {
543
938
  this.parseMask();
544
939
  }
545
- toRawValue(value, field) {
940
+ toRawValue(value, _field) {
546
941
  return this.applyMask(value);
547
942
  }
548
- toValueClear(value, field) {
943
+ toValueClear(value, _field) {
549
944
  return this.removeMask(value);
550
945
  }
551
946
  parseMask() {
@@ -744,8 +1139,12 @@ var StringType = class extends FieldType {
744
1139
  this.options.messages?.maxLength ?? `\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u0430\u044F \u0434\u043B\u0438\u043D\u0430 ${this.options.maxLength} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432`
745
1140
  );
746
1141
  }
747
- if (this.options.pattern != null && !this.options.pattern.test(value)) {
748
- errors.push(this.options.messages?.pattern ?? "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442");
1142
+ if (this.options.pattern != null) {
1143
+ const { source, flags } = this.options.pattern;
1144
+ const stablePattern = new RegExp(source, flags.replace(/[gy]/g, ""));
1145
+ if (!stablePattern.test(value)) {
1146
+ errors.push(this.options.messages?.pattern ?? "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442");
1147
+ }
749
1148
  }
750
1149
  return errors;
751
1150
  }
@@ -758,15 +1157,21 @@ var StringType = class extends FieldType {
758
1157
  var NumberType = class extends FieldType {
759
1158
  cast(rawValue) {
760
1159
  if (rawValue === null || rawValue === void 0) return null;
761
- if (typeof rawValue === "number" && !isNaN(rawValue)) return rawValue;
1160
+ if (typeof rawValue === "number") return Number.isFinite(rawValue) ? rawValue : null;
762
1161
  if (typeof rawValue === "string") {
763
- const num = Number(rawValue);
764
- return isNaN(num) ? null : num;
1162
+ const normalized = rawValue.trim();
1163
+ if (normalized.length === 0) return null;
1164
+ const num = Number(normalized);
1165
+ return Number.isFinite(num) ? num : null;
765
1166
  }
766
1167
  return null;
767
1168
  }
768
1169
  validate(value) {
769
1170
  const errors = [];
1171
+ if (!Number.isFinite(value)) {
1172
+ errors.push(this.options.messages?.invalid ?? "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043A\u043E\u043D\u0435\u0447\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C");
1173
+ return errors;
1174
+ }
770
1175
  if (!Number.isInteger(value)) {
771
1176
  errors.push(this.options.messages?.invalid ?? "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C");
772
1177
  }
@@ -811,7 +1216,7 @@ var DecimalType = class extends FieldType {
811
1216
  if (typeof rawValue === "string") {
812
1217
  return this.castString(rawValue);
813
1218
  }
814
- if (typeof rawValue === "number" && !isNaN(rawValue)) {
1219
+ if (typeof rawValue === "number" && Number.isFinite(rawValue)) {
815
1220
  return rawValue;
816
1221
  }
817
1222
  return null;
@@ -827,13 +1232,13 @@ var DecimalType = class extends FieldType {
827
1232
  }
828
1233
  validate(value) {
829
1234
  const errors = [];
830
- value = typeof value === "number" ? this.numberToString(value) : value;
1235
+ value = typeof value === "number" ? String(value) : value;
831
1236
  if (!this.isValidNumberString(value)) {
832
1237
  errors.push(this.options.messages?.invalid ?? "\u041F\u0440\u0438\u043C\u0435\u0440: 1.00");
833
1238
  return errors;
834
1239
  }
835
- const maxDigits = this.options.maxDigits || 8;
836
- const decimalPlaces = this.options.decimalPlaces || 2;
1240
+ const maxDigits = this.options.maxDigits ?? 8;
1241
+ const decimalPlaces = this.options.decimalPlaces ?? 2;
837
1242
  const maxIntDigits = maxDigits - decimalPlaces;
838
1243
  const isNegative = value.startsWith("-");
839
1244
  const normalizedValue = isNegative ? value.substring(1) : value;
@@ -907,10 +1312,6 @@ var DecimalType = class extends FieldType {
907
1312
  if (dotCount > 1) return false;
908
1313
  return pattern.test(value);
909
1314
  }
910
- numberToString(value) {
911
- const decimalPlaces = this.options.decimalPlaces;
912
- return value.toFixed(decimalPlaces);
913
- }
914
1315
  getTypeName() {
915
1316
  return "\u0434\u0435\u0441\u044F\u0442\u0438\u0447\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E";
916
1317
  }
@@ -987,7 +1388,7 @@ var DateType = class extends FieldType {
987
1388
  getTypeName() {
988
1389
  return this.options.isDatetime ? "\u0434\u0430\u0442\u0430 \u0438 \u0432\u0440\u0435\u043C\u044F" : "\u0434\u0430\u0442\u0430";
989
1390
  }
990
- // Сравнение дат с учётом времени
1391
+ // Compare dates with optional time precision.
991
1392
  compareDates(value, boundary, type) {
992
1393
  if (this.options.isDatetime) {
993
1394
  if (type === "min") {
@@ -1010,6 +1411,6 @@ var DateType = class extends FieldType {
1010
1411
  }
1011
1412
  };
1012
1413
 
1013
- export { ArrayField, ArrayFormField, BasePlugin, BooleanType, DateType, DecimalType, Field, FieldType, Form, MaskPlugin, NumberType, StringType };
1414
+ export { ArrayField, ArrayFormField, BasePlugin, BooleanType, DateType, DecimalType, DefaultErrorsSchema, ErrorsSchema, Field, FieldType, Form, MaskPlugin, NumberType, StringType };
1014
1415
  //# sourceMappingURL=index.mjs.map
1015
1416
  //# sourceMappingURL=index.mjs.map