adaptform 2.0.4 → 2.0.5

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);
@@ -26,11 +26,11 @@ var Field = class {
26
26
  });
27
27
  }
28
28
  get options() {
29
- return this.filed_type.options;
29
+ return this.fieldType.options;
30
30
  }
31
31
  set options(options) {
32
- let cur_options = this.filed_type.options;
33
- this.filed_type.options = Object.assign(cur_options, options);
32
+ let cur_options = this.fieldType.options;
33
+ this.fieldType.options = Object.assign(cur_options, options);
34
34
  }
35
35
  get rawValue() {
36
36
  return this._rawValue;
@@ -49,7 +49,7 @@ var Field = class {
49
49
  }
50
50
  }
51
51
  this._rawValue = rawValue;
52
- this._valueClear = this.filed_type.cast(valueClear);
52
+ this._valueClear = this.fieldType.cast(valueClear);
53
53
  this._errors = [];
54
54
  this.isTouched = true;
55
55
  this.checkAndSetValid();
@@ -92,11 +92,11 @@ var Field = class {
92
92
  }
93
93
  if (this._valueClear === null) {
94
94
  this._errors.push(
95
- this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.filed_type.getTypeName()}`
95
+ this.options.messages?.type ?? `\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F ${this.fieldType.getTypeName()}`
96
96
  );
97
97
  return false;
98
98
  }
99
- const typeErrors = this.filed_type.validate(this._valueClear, form);
99
+ const typeErrors = this.fieldType.validate(this._valueClear, form);
100
100
  if (typeErrors.length > 0) {
101
101
  this._errors.push(...typeErrors);
102
102
  return false;
@@ -129,7 +129,6 @@ var Field = class {
129
129
  reset() {
130
130
  if (this.defaultValue !== null && this.defaultValue !== void 0) {
131
131
  this.rawValue = this.defaultValue;
132
- this.isValid = true;
133
132
  } else {
134
133
  this._rawValue = null;
135
134
  this._valueClear = null;
@@ -153,6 +152,7 @@ var ArrayField = class {
153
152
  ...options
154
153
  };
155
154
  this._items = initial.map((v) => this.factory(v));
155
+ this.checkValid();
156
156
  }
157
157
  // ===== value =====
158
158
  set itemsValue(items) {
@@ -220,7 +220,8 @@ var ArrayField = class {
220
220
  if (!ok) valid = false;
221
221
  }
222
222
  if (this.options.validate) {
223
- const result = this.options.validate(this._items, form);
223
+ const values = this._items.map((i) => i.valueClear);
224
+ const result = this.options.validate(values, form);
224
225
  if (result === false) {
225
226
  this._errors.push(
226
227
  this.options.messages?.validate ?? "\u041E\u0448\u0438\u0431\u043A\u0430 \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438"
@@ -238,7 +239,8 @@ var ArrayField = class {
238
239
  return this.checkValid(form);
239
240
  }
240
241
  get isValid() {
241
- return this._errors.length === 0 && this._items.every((i) => i.isValid === true);
242
+ if (this._errors.length > 0) return false;
243
+ return this._items.every((i) => i.isValid === true);
242
244
  }
243
245
  get error() {
244
246
  return this._errors[0] ?? "";
@@ -273,6 +275,7 @@ var ArrayFormField = class {
273
275
  form.fields = data;
274
276
  return form;
275
277
  });
278
+ this.checkValid();
276
279
  }
277
280
  // ===== value =====
278
281
  set forms(forms) {
@@ -364,7 +367,8 @@ var ArrayFormField = class {
364
367
  return this.checkValid(form);
365
368
  }
366
369
  get isValid() {
367
- return this._errors.length === 0 && this._forms.every((i) => i.checkValid() === true);
370
+ if (this._errors.length > 0) return false;
371
+ return this._forms.every((i) => i.checkValid() === true);
368
372
  }
369
373
  get errors() {
370
374
  return this._errors;
@@ -381,10 +385,255 @@ var ArrayFormField = class {
381
385
  }
382
386
  };
383
387
 
388
+ // src/core/ErrorsShema.ts
389
+ var ErrorsSchema = class _ErrorsSchema {
390
+ constructor() {
391
+ this._staticRules = /* @__PURE__ */ new Map();
392
+ this._dynamicRules = [];
393
+ this._valueTransformers = [];
394
+ }
395
+ /**
396
+ * Добавляет статическое правило - точное соответствие ключа ошибки
397
+ * @param errorKey - точный ключ ошибки из ответа сервера
398
+ * @param fieldName - имя поля в форме или функция для динамического определения
399
+ */
400
+ staticKey(errorKey, fieldName) {
401
+ this._staticRules.set(errorKey, fieldName);
402
+ return this;
403
+ }
404
+ /**
405
+ * Добавляет динамическое правило с регулярным выражением
406
+ * @param pattern - регулярное выражение для сопоставления ключа ошибки
407
+ * @param mapping - имя поля или функция для извлечения имени поля из совпадения
408
+ * @param options - дополнительные опции (приоритет и т.д.)
409
+ */
410
+ dynamicKey(pattern, mapping, options) {
411
+ this._dynamicRules.push({
412
+ pattern,
413
+ mapping,
414
+ priority: options?.priority ?? 0
415
+ });
416
+ this._dynamicRules.sort((a, b) => b.priority - a.priority);
417
+ return this;
418
+ }
419
+ /**
420
+ * Устанавливает обработчик для ошибок, которые не подошли ни под одно правило
421
+ */
422
+ fallback(handler) {
423
+ this._fallbackHandler = handler;
424
+ return this;
425
+ }
426
+ /**
427
+ * Добавляет трансформер для значений ошибок
428
+ * Полезно для нормализации сообщений (например, если сервер возвращает массив ошибок)
429
+ */
430
+ transformValue(transformer) {
431
+ this._valueTransformers.push(transformer);
432
+ return this;
433
+ }
434
+ /**
435
+ * Быстрый метод для создания схемы из простого объекта маппинга
436
+ */
437
+ static fromObject(mapping) {
438
+ const schema = new _ErrorsSchema();
439
+ for (const [errorKey, fieldName] of Object.entries(mapping)) {
440
+ schema.staticKey(errorKey, fieldName);
441
+ }
442
+ return schema;
443
+ }
444
+ /**
445
+ * Создает схему для массива однотипных форм
446
+ */
447
+ static forArray(itemSchema, arrayFieldName, indexExtractor) {
448
+ const schema = new _ErrorsSchema();
449
+ schema.dynamicKey(
450
+ new RegExp(`^${arrayFieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\[(\\d+)\\]\\.(.+)$`),
451
+ (match) => {
452
+ const index = match[1];
453
+ const fieldName = match[2];
454
+ return `${arrayFieldName}.${index}.${fieldName}`;
455
+ },
456
+ { priority: 100 }
457
+ );
458
+ return schema;
459
+ }
460
+ /**
461
+ * Основной метод парсинга ошибок с поддержкой трансформеров
462
+ * Поддерживает вложенные структуры через точечную нотацию
463
+ */
464
+ parseErrors(errors, prefix = "") {
465
+ const result = {};
466
+ for (const [key, value] of Object.entries(errors)) {
467
+ const fullKey = prefix ? `${prefix}.${key}` : key;
468
+ if (this.isNestedObject(value)) {
469
+ const nestedErrors = this.parseErrors(value, fullKey);
470
+ Object.assign(result, nestedErrors);
471
+ continue;
472
+ }
473
+ if (value === null || value === void 0) {
474
+ continue;
475
+ }
476
+ let transformedValue = value;
477
+ for (const transformer of this._valueTransformers) {
478
+ transformedValue = transformer(transformedValue);
479
+ }
480
+ const mapping = this.findMapping(fullKey, transformedValue);
481
+ if (mapping) {
482
+ if (typeof mapping === "function") {
483
+ const fieldName = mapping(transformedValue);
484
+ if (fieldName) {
485
+ result[fieldName] = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
486
+ }
487
+ } else {
488
+ result[mapping] = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
489
+ }
490
+ } else if (this._fallbackHandler) {
491
+ const fieldName = this._fallbackHandler(fullKey, transformedValue);
492
+ if (fieldName) {
493
+ result[fieldName] = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
494
+ }
495
+ }
496
+ }
497
+ return result;
498
+ }
499
+ /**
500
+ * Парсит ошибки сервера и возвращает расширенную информацию
501
+ */
502
+ parseErrorsDetailed(errors, prefix = "") {
503
+ const result = {};
504
+ for (const [key, value] of Object.entries(errors)) {
505
+ const fullKey = prefix ? `${prefix}.${key}` : key;
506
+ if (this.isNestedObject(value)) {
507
+ const nestedErrors = this.parseErrorsDetailed(value, fullKey);
508
+ Object.assign(result, nestedErrors);
509
+ continue;
510
+ }
511
+ if (value === null || value === void 0) {
512
+ continue;
513
+ }
514
+ let transformedValue = value;
515
+ for (const transformer of this._valueTransformers) {
516
+ transformedValue = transformer(transformedValue);
517
+ }
518
+ const matchResult = this.findMappingDetailed(fullKey, transformedValue);
519
+ if (matchResult) {
520
+ const message = typeof transformedValue === "string" ? transformedValue : JSON.stringify(transformedValue);
521
+ result[matchResult.fieldName] = {
522
+ fieldName: matchResult.fieldName,
523
+ message,
524
+ sourceKey: fullKey,
525
+ matchedBy: matchResult.matchedBy
526
+ };
527
+ }
528
+ }
529
+ return result;
530
+ }
531
+ /**
532
+ * Создает схему для вложенной формы
533
+ */
534
+ nest(prefix) {
535
+ const nestedSchema = new _ErrorsSchema();
536
+ for (const [key, mapping] of this._staticRules.entries()) {
537
+ nestedSchema.staticKey(`${prefix}.${key}`, mapping);
538
+ }
539
+ for (const rule of this._dynamicRules) {
540
+ const newPattern = new RegExp(`^${prefix}\\.${rule.pattern.source.replace(/^\^/, "")}`);
541
+ nestedSchema.dynamicKey(newPattern, rule.mapping, { priority: rule.priority });
542
+ }
543
+ for (const transformer of this._valueTransformers) {
544
+ nestedSchema.transformValue(transformer);
545
+ }
546
+ if (this._fallbackHandler) {
547
+ nestedSchema.fallback((key, value) => {
548
+ if (key.startsWith(`${prefix}.`)) {
549
+ return this._fallbackHandler(key.slice(prefix.length + 1), value);
550
+ }
551
+ return null;
552
+ });
553
+ }
554
+ return nestedSchema;
555
+ }
556
+ /**
557
+ * Клонирует текущую схему
558
+ */
559
+ clone() {
560
+ const cloned = new _ErrorsSchema();
561
+ for (const [key, mapping] of this._staticRules.entries()) {
562
+ cloned._staticRules.set(key, mapping);
563
+ }
564
+ for (const rule of this._dynamicRules) {
565
+ cloned._dynamicRules.push({ ...rule });
566
+ }
567
+ cloned._fallbackHandler = this._fallbackHandler;
568
+ cloned._valueTransformers = [...this._valueTransformers];
569
+ return cloned;
570
+ }
571
+ /**
572
+ * Объединяет текущую схему с другой
573
+ */
574
+ merge(other) {
575
+ const merged = this.clone();
576
+ for (const [key, mapping] of other._staticRules.entries()) {
577
+ merged._staticRules.set(key, mapping);
578
+ }
579
+ merged._dynamicRules.push(...other._dynamicRules);
580
+ merged._dynamicRules.sort((a, b) => b.priority - a.priority);
581
+ merged._valueTransformers.push(...other._valueTransformers);
582
+ if (other._fallbackHandler) {
583
+ merged._fallbackHandler = other._fallbackHandler;
584
+ }
585
+ return merged;
586
+ }
587
+ isNestedObject(value) {
588
+ return value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
589
+ }
590
+ findMappingDetailed(key, value) {
591
+ if (this._staticRules.has(key)) {
592
+ const mapping = this._staticRules.get(key);
593
+ const fieldName = typeof mapping === "function" ? mapping(value) : mapping;
594
+ return fieldName ? { fieldName, matchedBy: "static" } : null;
595
+ }
596
+ for (const rule of this._dynamicRules) {
597
+ const match = key.match(rule.pattern);
598
+ if (match) {
599
+ const fieldName = typeof rule.mapping === "function" ? rule.mapping(match) : rule.mapping;
600
+ return fieldName ? { fieldName, matchedBy: "dynamic" } : null;
601
+ }
602
+ }
603
+ if (this._fallbackHandler) {
604
+ const fieldName = this._fallbackHandler(key, value);
605
+ return fieldName ? { fieldName, matchedBy: "fallback" } : null;
606
+ }
607
+ return null;
608
+ }
609
+ findMapping(key, value) {
610
+ if (this._staticRules.has(key)) {
611
+ return this._staticRules.get(key);
612
+ }
613
+ for (const rule of this._dynamicRules) {
614
+ const match = key.match(rule.pattern);
615
+ if (match) {
616
+ if (typeof rule.mapping === "function") {
617
+ const fieldName = rule.mapping(match);
618
+ return fieldName;
619
+ }
620
+ return rule.mapping;
621
+ }
622
+ }
623
+ if (this._fallbackHandler) {
624
+ const fieldName = this._fallbackHandler(key, value);
625
+ return fieldName;
626
+ }
627
+ return null;
628
+ }
629
+ };
630
+ var DefaultErrorsSchema = new ErrorsSchema().fallback((key) => key);
631
+
384
632
  // src/core/Form.ts
385
633
  var Form = class {
386
634
  constructor() {
387
635
  this._globalErrors = {};
636
+ this.errorsShema = DefaultErrorsSchema;
388
637
  }
389
638
  get _fields() {
390
639
  const fields = {};
@@ -437,9 +686,17 @@ var Form = class {
437
686
  const errors = {};
438
687
  const fields = this._fields;
439
688
  for (const key of Object.keys(fields)) {
440
- const fieldErrors = fields[key].errors;
441
- if (fieldErrors.length > 0) {
442
- errors[key] = fieldErrors;
689
+ const field = fields[key];
690
+ if (field instanceof ArrayFormField) {
691
+ const formErrors = this.collectArrayFormErrors(field);
692
+ if (formErrors.length > 0) {
693
+ errors[key] = formErrors;
694
+ }
695
+ } else {
696
+ const fieldErrors = field.errors;
697
+ if (fieldErrors.length > 0) {
698
+ errors[key] = fieldErrors;
699
+ }
443
700
  }
444
701
  }
445
702
  for (const key of Object.keys(this._globalErrors)) {
@@ -450,20 +707,84 @@ var Form = class {
450
707
  }
451
708
  return errors;
452
709
  }
710
+ /**
711
+ * Рекурсивно собирает все ошибки из ArrayFormField
712
+ */
713
+ collectArrayFormErrors(arrayField) {
714
+ const allErrors = [];
715
+ allErrors.push(...arrayField.errors);
716
+ for (let i = 0; i < arrayField.forms.length; i++) {
717
+ const form = arrayField.forms[i];
718
+ const formAllErrors = form.allErrors;
719
+ for (const [fieldName, fieldErrors] of Object.entries(formAllErrors)) {
720
+ for (const error of fieldErrors) {
721
+ allErrors.push(`\u0424\u043E\u0440\u043C\u0430 ${i + 1}, ${fieldName}: ${error}`);
722
+ }
723
+ }
724
+ }
725
+ return allErrors;
726
+ }
453
727
  get globalErrors() {
454
728
  return { ...this._globalErrors };
455
729
  }
456
730
  set errors(errors) {
457
731
  const fields = this._fields;
458
732
  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];
733
+ for (const key of Object.keys(fields)) {
734
+ fields[key].error = null;
735
+ }
736
+ const parsedErrors = this.errorsShema.parseErrors(errors);
737
+ for (const [fieldName, errorMessage] of Object.entries(parsedErrors)) {
738
+ this.applyErrorToField(fieldName, errorMessage, fields, this);
739
+ }
740
+ }
741
+ /**
742
+ * Применяет ошибку к соответствующему полю
743
+ * Поддерживает вложенные поля через точечную нотацию
744
+ */
745
+ applyErrorToField(fieldPath, errorMessage, fields, targetForm) {
746
+ if (!targetForm) {
747
+ console.error("applyErrorToField: targetForm is undefined");
748
+ return;
749
+ }
750
+ if (fieldPath in fields && fields[fieldPath]) {
751
+ fields[fieldPath].error = errorMessage;
752
+ return;
753
+ }
754
+ const parts = fieldPath.split(".");
755
+ const rootField = parts[0];
756
+ if (rootField in fields && fields[rootField]) {
757
+ const field = fields[rootField];
758
+ if (parts.length === 1) {
759
+ field.error = errorMessage;
760
+ } else if (parts.length > 1) {
761
+ this.applyNestedError(field, parts.slice(1), errorMessage, fieldPath, targetForm);
762
+ }
763
+ } else {
764
+ targetForm.setGlobalError(fieldPath, errorMessage);
765
+ }
766
+ }
767
+ applyNestedError(field, pathParts, errorMessage, fullPath, targetForm) {
768
+ if (!targetForm) {
769
+ console.error("applyNestedError: targetForm is undefined");
770
+ return;
771
+ }
772
+ const [currentPart, ...remainingParts] = pathParts;
773
+ if (field instanceof ArrayFormField) {
774
+ const index = parseInt(currentPart);
775
+ if (!isNaN(index) && index >= 0 && index < field.forms.length) {
776
+ const form = field.forms[index];
777
+ if (remainingParts.length > 0) {
778
+ const formFields = form["_fields"];
779
+ this.applyErrorToField(remainingParts.join("."), errorMessage, formFields, form);
780
+ } else {
781
+ form.setGlobalError(`index_${index}`, errorMessage);
782
+ }
464
783
  } else {
465
- this._globalErrors[key] = errors[key];
784
+ targetForm.setGlobalError(fullPath, errorMessage);
466
785
  }
786
+ } else {
787
+ targetForm.setGlobalError(fullPath, errorMessage);
467
788
  }
468
789
  }
469
790
  setGlobalError(key, message) {