@reformer/core 1.1.0 → 2.0.0-beta.10

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.
Files changed (221) hide show
  1. package/dist/behaviors/compute-from.d.ts +2 -0
  2. package/dist/behaviors/compute-from.js +31 -0
  3. package/dist/behaviors/copy-from.d.ts +2 -0
  4. package/dist/behaviors/copy-from.js +29 -0
  5. package/dist/behaviors/enable-when.d.ts +2 -0
  6. package/dist/behaviors/enable-when.js +25 -0
  7. package/dist/behaviors/reset-when.d.ts +2 -0
  8. package/dist/behaviors/reset-when.js +24 -0
  9. package/dist/behaviors/revalidate-when.d.ts +2 -0
  10. package/dist/behaviors/revalidate-when.js +18 -0
  11. package/dist/behaviors/sync-fields.d.ts +2 -0
  12. package/dist/behaviors/sync-fields.js +41 -0
  13. package/dist/behaviors/transform-value.d.ts +2 -0
  14. package/dist/behaviors/transform-value.js +45 -0
  15. package/dist/behaviors/watch-field.d.ts +2 -0
  16. package/dist/behaviors/watch-field.js +21 -0
  17. package/dist/behaviors.d.ts +6 -2
  18. package/dist/behaviors.js +27 -228
  19. package/dist/core/behavior/behavior-context.d.ts +32 -14
  20. package/dist/core/behavior/behavior-registry.d.ts +15 -27
  21. package/dist/core/behavior/behaviors/compute-from.d.ts +50 -21
  22. package/dist/core/behavior/behaviors/copy-from.d.ts +39 -14
  23. package/dist/core/behavior/behaviors/enable-when.d.ts +88 -19
  24. package/dist/core/behavior/behaviors/reset-when.d.ts +31 -18
  25. package/dist/core/behavior/behaviors/revalidate-when.d.ts +40 -17
  26. package/dist/core/behavior/behaviors/sync-fields.d.ts +34 -14
  27. package/dist/core/behavior/behaviors/transform-value.d.ts +116 -44
  28. package/dist/core/behavior/behaviors/watch-field.d.ts +66 -21
  29. package/dist/core/behavior/compose-behavior.d.ts +2 -12
  30. package/dist/core/behavior/index.d.ts +0 -1
  31. package/dist/core/behavior/types.d.ts +2 -8
  32. package/dist/core/factories/node-factory.d.ts +6 -29
  33. package/dist/core/nodes/array-node.d.ts +42 -22
  34. package/dist/core/nodes/field-node.d.ts +51 -26
  35. package/dist/core/nodes/form-node.d.ts +18 -20
  36. package/dist/core/nodes/group-node.d.ts +37 -212
  37. package/dist/core/types/deep-schema.d.ts +2 -12
  38. package/dist/core/types/field-path.d.ts +1 -1
  39. package/dist/core/types/form-context.d.ts +36 -30
  40. package/dist/core/types/{group-node-proxy.d.ts → form-proxy.d.ts} +12 -42
  41. package/dist/core/types/index.d.ts +52 -6
  42. package/dist/core/types/validation-schema.d.ts +3 -12
  43. package/dist/core/utils/abstract-registry.d.ts +74 -0
  44. package/dist/core/utils/aggregate-signals.d.ts +71 -0
  45. package/dist/core/utils/create-form.d.ts +3 -20
  46. package/dist/core/utils/error-handler.d.ts +1 -18
  47. package/dist/core/utils/field-path-navigator.d.ts +1 -1
  48. package/dist/core/{validation → utils}/field-path.d.ts +23 -6
  49. package/dist/core/utils/form-observer.d.ts +176 -0
  50. package/dist/core/utils/form-proxy-builder.d.ts +25 -0
  51. package/dist/core/utils/form-submitter.d.ts +121 -0
  52. package/dist/core/utils/index.d.ts +10 -2
  53. package/dist/core/utils/registry-helpers.d.ts +0 -7
  54. package/dist/core/utils/safe-effect.d.ts +73 -0
  55. package/dist/core/utils/status-machine.d.ts +153 -0
  56. package/dist/core/utils/type-guards.d.ts +5 -23
  57. package/dist/core/utils/unique-id.d.ts +53 -0
  58. package/dist/core/validation/core/apply-when.d.ts +3 -9
  59. package/dist/core/validation/core/apply.d.ts +2 -13
  60. package/dist/core/validation/core/validate-async.d.ts +2 -8
  61. package/dist/core/validation/core/validate-tree.d.ts +10 -10
  62. package/dist/core/validation/core/validate.d.ts +1 -7
  63. package/dist/core/validation/index.d.ts +8 -2
  64. package/dist/core/validation/validate-form.d.ts +1 -38
  65. package/dist/core/validation/validation-applicator.d.ts +2 -21
  66. package/dist/core/validation/validation-context.d.ts +67 -28
  67. package/dist/core/validation/validation-registry.d.ts +11 -25
  68. package/dist/core/validation/validators/array-validators.d.ts +2 -12
  69. package/dist/core/validation/validators/date-utils.d.ts +26 -0
  70. package/dist/core/validation/validators/email.d.ts +2 -9
  71. package/dist/core/validation/validators/future-date.d.ts +35 -0
  72. package/dist/core/validation/validators/index.d.ts +7 -1
  73. package/dist/core/validation/validators/is-date.d.ts +36 -0
  74. package/dist/core/validation/validators/max-age.d.ts +36 -0
  75. package/dist/core/validation/validators/max-date.d.ts +36 -0
  76. package/dist/core/validation/validators/max-length.d.ts +3 -10
  77. package/dist/core/validation/validators/max.d.ts +3 -10
  78. package/dist/core/validation/validators/min-age.d.ts +36 -0
  79. package/dist/core/validation/validators/min-date.d.ts +36 -0
  80. package/dist/core/validation/validators/min-length.d.ts +3 -10
  81. package/dist/core/validation/validators/min.d.ts +3 -10
  82. package/dist/core/validation/validators/number.d.ts +2 -9
  83. package/dist/core/validation/validators/past-date.d.ts +35 -0
  84. package/dist/core/validation/validators/pattern.d.ts +2 -9
  85. package/dist/core/validation/validators/phone.d.ts +2 -9
  86. package/dist/core/validation/validators/required.d.ts +2 -9
  87. package/dist/core/validation/validators/url.d.ts +2 -9
  88. package/dist/date-utils-xUWFslTj.js +29 -0
  89. package/dist/field-path-DuKdGcIE.js +66 -0
  90. package/dist/hooks/types.d.ts +328 -0
  91. package/dist/hooks/useArrayLength.d.ts +31 -0
  92. package/dist/hooks/useFormControl.d.ts +15 -39
  93. package/dist/hooks/useFormControlValue.d.ts +167 -0
  94. package/dist/hooks/useHiddenCondition.d.ts +25 -0
  95. package/dist/hooks/useSignalSubscription.d.ts +17 -0
  96. package/dist/index-D25LsbRm.js +73 -0
  97. package/dist/index.d.ts +8 -1
  98. package/dist/index.js +3271 -8
  99. package/dist/registry-helpers-Bv_BJ1s-.js +615 -0
  100. package/dist/safe-effect-Dh8uw81c.js +20 -0
  101. package/dist/validate-C3XiA_zf.js +10 -0
  102. package/dist/validators/email.d.ts +2 -0
  103. package/dist/validators/email.js +13 -0
  104. package/dist/validators/future-date.d.ts +2 -0
  105. package/dist/validators/future-date.js +20 -0
  106. package/dist/validators/is-date.d.ts +2 -0
  107. package/dist/validators/is-date.js +12 -0
  108. package/dist/validators/max-age.d.ts +2 -0
  109. package/dist/validators/max-age.js +20 -0
  110. package/dist/validators/max-date.d.ts +2 -0
  111. package/dist/validators/max-date.js +20 -0
  112. package/dist/validators/max-length.d.ts +2 -0
  113. package/dist/validators/max-length.js +11 -0
  114. package/dist/validators/max.d.ts +2 -0
  115. package/dist/validators/max.js +11 -0
  116. package/dist/validators/min-age.d.ts +2 -0
  117. package/dist/validators/min-age.js +20 -0
  118. package/dist/validators/min-date.d.ts +2 -0
  119. package/dist/validators/min-date.js +20 -0
  120. package/dist/validators/min-length.d.ts +2 -0
  121. package/dist/validators/min-length.js +11 -0
  122. package/dist/validators/min.d.ts +2 -0
  123. package/dist/validators/min.js +11 -0
  124. package/dist/validators/number.d.ts +2 -0
  125. package/dist/validators/number.js +35 -0
  126. package/dist/validators/past-date.d.ts +2 -0
  127. package/dist/validators/past-date.js +20 -0
  128. package/dist/validators/pattern.d.ts +2 -0
  129. package/dist/validators/pattern.js +11 -0
  130. package/dist/validators/phone.d.ts +2 -0
  131. package/dist/validators/phone.js +35 -0
  132. package/dist/validators/required.d.ts +2 -0
  133. package/dist/validators/required.js +15 -0
  134. package/dist/validators/url.d.ts +2 -0
  135. package/dist/validators/url.js +19 -0
  136. package/dist/validators-BGsNOgT1.js +207 -0
  137. package/dist/validators.d.ts +6 -2
  138. package/dist/validators.js +54 -296
  139. package/llms.txt +8887 -59
  140. package/package.json +87 -8
  141. package/dist/core/behavior/behavior-applicator.d.ts +0 -71
  142. package/dist/core/behavior/behavior-applicator.js +0 -92
  143. package/dist/core/behavior/behavior-context.js +0 -38
  144. package/dist/core/behavior/behavior-registry.js +0 -198
  145. package/dist/core/behavior/behaviors/compute-from.js +0 -84
  146. package/dist/core/behavior/behaviors/copy-from.js +0 -64
  147. package/dist/core/behavior/behaviors/enable-when.js +0 -81
  148. package/dist/core/behavior/behaviors/index.js +0 -11
  149. package/dist/core/behavior/behaviors/reset-when.js +0 -63
  150. package/dist/core/behavior/behaviors/revalidate-when.js +0 -51
  151. package/dist/core/behavior/behaviors/sync-fields.js +0 -66
  152. package/dist/core/behavior/behaviors/transform-value.js +0 -110
  153. package/dist/core/behavior/behaviors/watch-field.js +0 -56
  154. package/dist/core/behavior/compose-behavior.js +0 -166
  155. package/dist/core/behavior/create-field-path.d.ts +0 -20
  156. package/dist/core/behavior/create-field-path.js +0 -69
  157. package/dist/core/behavior/index.js +0 -17
  158. package/dist/core/behavior/types.js +0 -7
  159. package/dist/core/context/form-context-impl.d.ts +0 -29
  160. package/dist/core/context/form-context-impl.js +0 -37
  161. package/dist/core/factories/index.js +0 -6
  162. package/dist/core/factories/node-factory.js +0 -281
  163. package/dist/core/nodes/array-node.js +0 -534
  164. package/dist/core/nodes/field-node.js +0 -510
  165. package/dist/core/nodes/form-node.js +0 -343
  166. package/dist/core/nodes/group-node/field-registry.d.ts +0 -191
  167. package/dist/core/nodes/group-node/field-registry.js +0 -215
  168. package/dist/core/nodes/group-node/index.d.ts +0 -11
  169. package/dist/core/nodes/group-node/index.js +0 -11
  170. package/dist/core/nodes/group-node/proxy-builder.d.ts +0 -71
  171. package/dist/core/nodes/group-node/proxy-builder.js +0 -161
  172. package/dist/core/nodes/group-node/state-manager.d.ts +0 -184
  173. package/dist/core/nodes/group-node/state-manager.js +0 -265
  174. package/dist/core/nodes/group-node.js +0 -770
  175. package/dist/core/types/deep-schema.js +0 -11
  176. package/dist/core/types/field-path.js +0 -4
  177. package/dist/core/types/form-context.js +0 -25
  178. package/dist/core/types/group-node-proxy.js +0 -31
  179. package/dist/core/types/index.js +0 -4
  180. package/dist/core/types/validation-schema.js +0 -10
  181. package/dist/core/utils/create-form.js +0 -24
  182. package/dist/core/utils/debounce.d.ts +0 -160
  183. package/dist/core/utils/debounce.js +0 -197
  184. package/dist/core/utils/error-handler.js +0 -226
  185. package/dist/core/utils/field-path-navigator.js +0 -374
  186. package/dist/core/utils/index.js +0 -14
  187. package/dist/core/utils/registry-helpers.js +0 -79
  188. package/dist/core/utils/registry-stack.js +0 -86
  189. package/dist/core/utils/resources.d.ts +0 -41
  190. package/dist/core/utils/resources.js +0 -69
  191. package/dist/core/utils/subscription-manager.js +0 -214
  192. package/dist/core/utils/type-guards.js +0 -169
  193. package/dist/core/validation/core/apply-when.js +0 -41
  194. package/dist/core/validation/core/apply.js +0 -38
  195. package/dist/core/validation/core/index.js +0 -8
  196. package/dist/core/validation/core/validate-async.js +0 -45
  197. package/dist/core/validation/core/validate-tree.js +0 -37
  198. package/dist/core/validation/core/validate.js +0 -38
  199. package/dist/core/validation/field-path.js +0 -147
  200. package/dist/core/validation/index.js +0 -33
  201. package/dist/core/validation/validate-form.js +0 -152
  202. package/dist/core/validation/validation-applicator.js +0 -217
  203. package/dist/core/validation/validation-context.js +0 -75
  204. package/dist/core/validation/validation-registry.js +0 -298
  205. package/dist/core/validation/validators/array-validators.js +0 -86
  206. package/dist/core/validation/validators/date.d.ts +0 -38
  207. package/dist/core/validation/validators/date.js +0 -117
  208. package/dist/core/validation/validators/email.js +0 -60
  209. package/dist/core/validation/validators/index.js +0 -14
  210. package/dist/core/validation/validators/max-length.js +0 -60
  211. package/dist/core/validation/validators/max.js +0 -60
  212. package/dist/core/validation/validators/min-length.js +0 -60
  213. package/dist/core/validation/validators/min.js +0 -60
  214. package/dist/core/validation/validators/number.js +0 -90
  215. package/dist/core/validation/validators/pattern.js +0 -62
  216. package/dist/core/validation/validators/phone.js +0 -58
  217. package/dist/core/validation/validators/required.js +0 -69
  218. package/dist/core/validation/validators/url.js +0 -55
  219. package/dist/create-field-path-CdPF3lIK.js +0 -704
  220. package/dist/hooks/useFormControl.js +0 -298
  221. package/dist/node-factory-D7DOnSSN.js +0 -3200
@@ -1,11 +1,4 @@
1
- /**
2
- * Трансформация значений полей
3
- *
4
- * @group Behaviors
5
- * @category Behavior Rules
6
- * @module behaviors/transformValue
7
- */
8
- import type { FieldPathNode, FormFields, FormValue } from '../../types';
1
+ import { FieldPathNode, FormFields, FormValue } from '../../types';
9
2
  /**
10
3
  * Опции для transformValue
11
4
  *
@@ -26,34 +19,52 @@ export interface TransformValueOptions {
26
19
  * @category Behavior Rules
27
20
  *
28
21
  * @param field - Поле для трансформации
29
- * @param transformer - Функция трансформации
30
- * @param options - Опции
22
+ * @param transformer - Функция трансформации (ОБЯЗАТЕЛЬНО идемпотентная: f(f(x)) === f(x))
23
+ * @param options - Опции (`onUserChangeOnly`, `emitEvent`, `debounce`)
24
+ *
25
+ * @example Базовая нормализация — uppercase + trim email
26
+ * ```typescript
27
+ * import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
28
+ *
29
+ * interface RegistrationForm {
30
+ * promoCode: string;
31
+ * email: string;
32
+ * }
33
+ *
34
+ * export const registrationBehavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
35
+ * // Идемпотентно: toUpperCase(toUpperCase(x)) === toUpperCase(x) ✓
36
+ * transformValue(path.promoCode, (value) => (value ?? '').toUpperCase());
37
+ * transformValue(path.email, (value) => (value ?? '').trim().toLowerCase());
38
+ * };
39
+ * ```
31
40
  *
32
- * @example
41
+ * @example `onUserChangeOnly` + idempotent guard для не-тривиальных форматов
33
42
  * ```typescript
34
- * const schema: BehaviorSchemaFn<MyForm> = (path) => {
35
- * // Автоматически переводить текст в верхний регистр
36
- * transformValue(path.code, (value) => value?.toUpperCase());
37
- *
38
- * // Форматировать номер телефона
39
- * transformValue(path.phone, (value) => {
40
- * if (!value) return value;
41
- * const digits = value.replace(/\D/g, '');
42
- * if (digits.length === 11) {
43
- * return `+7 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7, 9)}-${digits.slice(9)}`;
44
- * }
45
- * return value;
46
- * });
47
- *
48
- * // Удалять пробелы из email
49
- * transformValue(path.email, (value) => value?.trim().toLowerCase());
50
- *
51
- * // Округлять числа
52
- * transformValue(path.amount, (value) => {
53
- * return typeof value === 'number' ? Math.round(value) : value;
54
- * });
43
+ * import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
44
+ *
45
+ * interface ProfileForm {
46
+ * inn: string; // ИНН — только цифры
47
+ * prefixedCode: string; // должен иметь префикс "ID-"
48
+ * }
49
+ *
50
+ * export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
51
+ * // Цифры из ИНН: трансформер идемпотентный естественно
52
+ * transformValue(path.inn, (v) => (v ?? '').replace(/\D/g, ''));
53
+ *
54
+ * // Префикс — ВАЖНО guard «уже преобразовано», иначе бесконечный цикл
55
+ * // f("ID-123") должно === "ID-123", а не "ID-ID-123"
56
+ * transformValue(
57
+ * path.prefixedCode,
58
+ * (v) => (v?.startsWith('ID-') ? v : `ID-${v ?? ''}`),
59
+ * {
60
+ * onUserChangeOnly: true, // не трогаем значение из patchValue/preload
61
+ * debounce: 200,
62
+ * },
63
+ * );
55
64
  * };
56
65
  * ```
66
+ *
67
+ * @see [docs/llms/26-transform-value.md](../../../../docs/llms/26-transform-value.md)
57
68
  */
58
69
  export declare function transformValue<TForm extends FormFields, TValue extends FormValue = FormValue>(field: FieldPathNode<TForm, TValue>, transformer: (value: TValue) => TValue, options?: TransformValueOptions & {
59
70
  debounce?: number;
@@ -64,18 +75,42 @@ export declare function transformValue<TForm extends FormFields, TValue extends
64
75
  * @group Behaviors
65
76
  * @category Behavior Rules
66
77
  *
67
- * @example
78
+ * @param transformer - Идемпотентная функция преобразования значения
79
+ * @param defaultOptions - Опции, применяемые ко всем вызовам созданного трансформера
80
+ *
81
+ * @example Доменно-специфичные трансформеры (банковский счёт, СНИЛС)
68
82
  * ```typescript
69
- * // Создаем переиспользуемые трансформеры
70
- * const toUpperCase = createTransformer<string>((value) => value?.toUpperCase());
71
- * const toLowerCase = createTransformer<string>((value) => value?.toLowerCase());
72
- * const trim = createTransformer<string>((value) => value?.trim());
73
- *
74
- * // Используем в форме
75
- * const schema: BehaviorSchemaFn<MyForm> = (path) => {
76
- * toUpperCase(path.code);
77
- * toLowerCase(path.email);
78
- * trim(path.username);
83
+ * import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
84
+ *
85
+ * // Сохраняем только цифры и форматируем СНИЛС: 000-000-000 00
86
+ * const formatSnils = createTransformer<string>((v) => {
87
+ * const d = (v ?? '').replace(/\D/g, '').slice(0, 11);
88
+ * if (d.length < 9) return d;
89
+ * return `${d.slice(0, 3)}-${d.slice(3, 6)}-${d.slice(6, 9)}${d.length > 9 ? ' ' + d.slice(9) : ''}`;
90
+ * });
91
+ *
92
+ * interface ProfileForm { snils: string }
93
+ *
94
+ * export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
95
+ * formatSnils(path.snils, { debounce: 100 });
96
+ * };
97
+ * ```
98
+ *
99
+ * @example С `defaultOptions` — единые настройки на серию полей
100
+ * ```typescript
101
+ * import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
102
+ *
103
+ * // Все коды должны быть uppercase, но только после правки пользователем
104
+ * const upperOnUserEdit = createTransformer<string>(
105
+ * (v) => (v ?? '').toUpperCase(),
106
+ * { onUserChangeOnly: true, debounce: 100 },
107
+ * );
108
+ *
109
+ * interface PromoForm { promoCode: string; partnerCode: string }
110
+ *
111
+ * export const promoBehavior: BehaviorSchemaFn<PromoForm> = (path) => {
112
+ * upperOnUserEdit(path.promoCode);
113
+ * upperOnUserEdit(path.partnerCode);
79
114
  * };
80
115
  * ```
81
116
  */
@@ -83,10 +118,47 @@ export declare function createTransformer<TValue extends FormValue = FormValue>(
83
118
  debounce?: number;
84
119
  }) => void;
85
120
  /**
86
- * Готовые трансформеры для частых случаев
121
+ * Готовые трансформеры для частых случаев. Все идемпотентны и безопасны для повторного применения.
87
122
  *
88
123
  * @group Behaviors
89
124
  * @category Behavior Rules
125
+ *
126
+ * @example Готовые трансформеры в схеме формы
127
+ * ```typescript
128
+ * import { transformers, type BehaviorSchemaFn } from '@reformer/core/behaviors';
129
+ *
130
+ * interface RegistrationForm {
131
+ * username: string;
132
+ * email: string;
133
+ * promoCode: string;
134
+ * inn: string;
135
+ * amount: number;
136
+ * }
137
+ *
138
+ * export const behavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
139
+ * transformers.trim(path.username);
140
+ * transformers.toLowerCase(path.email);
141
+ * transformers.toUpperCase(path.promoCode);
142
+ * transformers.digitsOnly(path.inn);
143
+ * transformers.roundTo2(path.amount);
144
+ * };
145
+ * ```
146
+ *
147
+ * @example Композиция готовых трансформеров через createTransformer
148
+ * ```typescript
149
+ * import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
150
+ *
151
+ * // trim + lowercase в одном трансформере (применяется как одна операция)
152
+ * const normalizeEmail = createTransformer<string>(
153
+ * (v) => (v ?? '').trim().toLowerCase(),
154
+ * );
155
+ *
156
+ * interface ContactForm { email: string }
157
+ *
158
+ * export const contactBehavior: BehaviorSchemaFn<ContactForm> = (path) => {
159
+ * normalizeEmail(path.email);
160
+ * };
161
+ * ```
90
162
  */
91
163
  export declare const transformers: {
92
164
  /** Перевести в верхний регистр */
@@ -1,12 +1,5 @@
1
- /**
2
- * Отслеживание изменений поля
3
- *
4
- * @group Behaviors
5
- * @category Behavior Rules
6
- * @module behaviors/watchField
7
- */
8
- import type { FieldPathNode } from '../../types';
9
- import type { BehaviorContext, WatchFieldOptions } from '../types';
1
+ import { FieldPathNode } from '../../types';
2
+ import { BehaviorContext, WatchFieldOptions } from '../types';
10
3
  /**
11
4
  * Выполняет callback при изменении поля
12
5
  *
@@ -15,21 +8,73 @@ import type { BehaviorContext, WatchFieldOptions } from '../types';
15
8
  *
16
9
  * @param field - Поле для отслеживания
17
10
  * @param callback - Функция обратного вызова
18
- * @param options - Опции
11
+ * @param options - Опции (`debounce`, `immediate`)
12
+ *
13
+ * @example Async loader with try/catch + guard + debounce
14
+ * ```typescript
15
+ * import { watchField, type BehaviorSchemaFn } from '@reformer/core/behaviors';
16
+ *
17
+ * interface AddressForm { region: string; city: string }
19
18
  *
20
- * @example
19
+ * export const addressBehavior: BehaviorSchemaFn<AddressForm> = (path) => {
20
+ * watchField(
21
+ * path.region,
22
+ * async (region, ctx) => {
23
+ * if (!region) {
24
+ * ctx.form.city.updateComponentProps({ options: [] });
25
+ * return; // guard: пустое значение не триггерит fetch
26
+ * }
27
+ * try {
28
+ * const { data: cities } = await fetchCities(region);
29
+ * ctx.form.city.updateComponentProps({ options: cities });
30
+ * } catch (error) {
31
+ * console.error('[addressBehavior] failed to load cities:', error);
32
+ * ctx.form.city.updateComponentProps({ options: [] });
33
+ * }
34
+ * },
35
+ * { immediate: false, debounce: 300 }, // обязательные опции для async
36
+ * );
37
+ * };
38
+ * ```
39
+ *
40
+ * @example Sync handler с консолидацией нескольких зависимостей в один watcher
21
41
  * ```typescript
22
- * const schema: BehaviorSchemaFn<MyForm> = (path) => {
23
- * // Динамическая загрузка городов при изменении страны
24
- * watchField(path.registrationAddress.country, async (country, ctx) => {
25
- * if (country) {
26
- * const cities = await fetchCities(country);
27
- * ctx.updateComponentProps(path.registrationAddress.city, {
28
- * options: cities
29
- * });
30
- * }
31
- * });
42
+ * import { watchField, type BehaviorSchemaFn } from '@reformer/core/behaviors';
43
+ *
44
+ * interface InsuranceForm {
45
+ * insuranceType: 'casco' | 'osago' | 'property' | '';
46
+ * vehicleVin: string;
47
+ * propertyType: string;
48
+ * }
49
+ *
50
+ * export const insuranceBehavior: BehaviorSchemaFn<InsuranceForm> = (path) => {
51
+ * // ОДИН watchField на trigger-поле — несколько watcher'ов на одно поле = "Cycle detected"
52
+ * watchField(
53
+ * path.insuranceType,
54
+ * (type, ctx) => {
55
+ * const isVehicle = type === 'casco' || type === 'osago';
56
+ * const isProperty = type === 'property';
57
+ *
58
+ * // Guard — ставим только если состояние реально меняется
59
+ * if (isVehicle) {
60
+ * if (ctx.form.vehicleVin.disabled.value) ctx.form.vehicleVin.enable();
61
+ * } else {
62
+ * if (!ctx.form.vehicleVin.disabled.value) ctx.form.vehicleVin.disable();
63
+ * if (ctx.form.vehicleVin.getValue() !== '') ctx.form.vehicleVin.setValue('');
64
+ * }
65
+ *
66
+ * if (isProperty) {
67
+ * if (ctx.form.propertyType.disabled.value) ctx.form.propertyType.enable();
68
+ * } else {
69
+ * if (!ctx.form.propertyType.disabled.value) ctx.form.propertyType.disable();
70
+ * if (ctx.form.propertyType.getValue() !== '') ctx.form.propertyType.setValue('');
71
+ * }
72
+ * },
73
+ * { immediate: false }, // CRITICAL: предотвращает запуск во время инициализации
74
+ * );
32
75
  * };
33
76
  * ```
77
+ *
78
+ * @see [docs/llms/22-cycle-detection.md](../../../../docs/llms/22-cycle-detection.md)
34
79
  */
35
80
  export declare function watchField<TForm, TField>(field: FieldPathNode<TForm, TField>, callback: (value: TField, ctx: BehaviorContext<TForm>) => void | Promise<void>, options?: WatchFieldOptions): void;
@@ -1,15 +1,5 @@
1
- /**
2
- * Композиция behavior схем
3
- *
4
- * Предоставляет функции для переиспользования behavior схем:
5
- * - toBehaviorFieldPath: преобразование FieldPath во вложенный путь
6
- * - apply: применение схемы к полям
7
- * - applyWhen: условное применение схемы
8
- *
9
- * Аналог toFieldPath и applyWhen из validation API.
10
- */
11
- import type { FieldPath, FieldPathNode, FormFields, FormValue } from '../types';
12
- import type { BehaviorSchemaFn } from './types';
1
+ import { FieldPath, FieldPathNode, FormFields, FormValue } from '../types';
2
+ import { BehaviorSchemaFn } from './types';
13
3
  /**
14
4
  * Преобразовать FieldPath во вложенный путь для композиции behavior схем
15
5
  *
@@ -9,4 +9,3 @@ export * from './behaviors';
9
9
  export { apply, applyWhen, toBehaviorFieldPath } from './compose-behavior';
10
10
  export { BehaviorRegistry } from './behavior-registry';
11
11
  export { BehaviorContextImpl } from './behavior-context';
12
- export { createFieldPath } from './create-field-path';
@@ -1,12 +1,6 @@
1
- /**
2
- * Типы и интерфейсы для Behavior Schema API
3
- *
4
- * @group Behaviors
5
- * @category Behavior Types
6
- */
7
1
  import { GroupNode } from '../nodes/group-node';
8
- import type { FieldPath } from '../types/field-path';
9
- import type { FormContext } from '../types/form-context';
2
+ import { FieldPath } from '../types/field-path';
3
+ import { FormContext } from '../types/form-context';
10
4
  /**
11
5
  * Тип функции behavior схемы
12
6
  * Принимает FieldPath и описывает поведение формы
@@ -1,40 +1,17 @@
1
+ import { FormNode } from '../nodes/form-node';
1
2
  /**
2
- * NodeFactory - фабрика для создания узлов формы
3
+ * Фабрика для создания узлов формы.
3
4
  *
4
- * Инкапсулирует логику определения типа конфига и создания соответствующего узла.
5
- * Используется в GroupNode и ArrayNode для создания дочерних узлов.
6
- *
7
- * Паттерн Factory Method упрощает создание узлов и делает код более читаемым:
8
- * - Вместо if-else в GroupNode/ArrayNode
9
- * - Единая точка для создания узлов
10
- * - Легко добавлять новые типы узлов
5
+ * Определяет тип конфига и создаёт соответствующий узел (FieldNode, GroupNode, ArrayNode).
6
+ * Используется внутри `getReformerForm`/`group`/`array` явно вызывать обычно не нужно.
11
7
  *
12
8
  * @example
13
9
  * ```typescript
14
- * const factory = new NodeFactory();
15
- *
16
- * // Создание FieldNode
17
- * const field = factory.createNode({ value: '', component: Input });
18
- *
19
- * // Создание GroupNode
20
- * const group = factory.createNode({
21
- * email: { value: '', component: Input },
22
- * password: { value: '', component: Input }
23
- * });
10
+ * import { NodeFactory } from '@reformer/core';
24
11
  *
25
- * // Создание ArrayNode
26
- * const array = factory.createNode({
27
- * schema: { title: { value: '', component: Input } },
28
- * initialItems: []
29
- * });
12
+ * const node = NodeFactory.create({ value: '' }); // FieldNode<string>
30
13
  * ```
31
14
  */
32
- import type { FormNode } from '../nodes/form-node';
33
- /**
34
- * Фабрика для создания узлов формы
35
- *
36
- * Определяет тип конфига и создает соответствующий узел (FieldNode, GroupNode, ArrayNode)
37
- */
38
15
  export declare class NodeFactory {
39
16
  /**
40
17
  * Создает узел формы на основе конфигурации
@@ -1,18 +1,8 @@
1
- /**
2
- * ArrayNode - узел формы для работы с массивами
3
- *
4
- * Управляет массивом форм с поддержкой:
5
- * - Динамического добавления/удаления элементов
6
- * - Валидации всех элементов
7
- * - Реактивного состояния через signals
8
- *
9
- * @group Nodes
10
- */
11
- import type { ReadonlySignal } from '@preact/signals-core';
12
- import { FormNode, type SetValueOptions } from './form-node';
13
- import type { FieldStatus, ValidationError, FormFields } from '../types';
14
- import type { FormSchema } from '../types/deep-schema';
15
- import type { GroupNodeWithControls } from '../types/group-node-proxy';
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { FormNode, SetValueOptions } from './form-node';
3
+ import { FieldStatus, ValidationError, FormFields } from '../types';
4
+ import { FormSchema } from '../types/deep-schema';
5
+ import { FormProxy } from '../types/form-proxy';
16
6
  /**
17
7
  * ArrayNode - массив форм с реактивным состоянием
18
8
  *
@@ -39,6 +29,8 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
39
29
  * Использует SubscriptionManager вместо массива для управления подписками
40
30
  */
41
31
  private disposers;
32
+ /** Array-level validation errors (e.g., "минимум 1 элемент") */
33
+ private readonly _arrayErrors;
42
34
  private validationSchemaFn?;
43
35
  private behaviorSchemaFn?;
44
36
  readonly value: ReadonlySignal<T[]>;
@@ -59,6 +51,9 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
59
51
  /**
60
52
  * Удалить элемент по индексу
61
53
  * @param index - Индекс элемента для удаления
54
+ *
55
+ * @remarks
56
+ * Вызывает dispose() на удаляемом элементе для очистки подписок
62
57
  */
63
58
  removeAt(index: number): void;
64
59
  /**
@@ -69,14 +64,17 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
69
64
  insert(index: number, initialValue?: Partial<T>): void;
70
65
  /**
71
66
  * Удалить все элементы массива
67
+ *
68
+ * @remarks
69
+ * Вызывает dispose() на всех элементах для очистки подписок
72
70
  */
73
71
  clear(): void;
74
72
  /**
75
73
  * Получить элемент по индексу
76
74
  * @param index - Индекс элемента
77
- * @returns Типизированный GroupNode или undefined если индекс вне границ
75
+ * @returns Типизированный GroupNode proxy или undefined если индекс вне границ
78
76
  */
79
- at(index: number): GroupNodeWithControls<T> | undefined;
77
+ at(index: number): FormProxy<T> | undefined;
80
78
  getValue(): T[];
81
79
  setValue(values: T[], options?: SetValueOptions): void;
82
80
  patchValue(values: (T | undefined)[]): void;
@@ -128,7 +126,29 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
128
126
  */
129
127
  resetToInitial(): void;
130
128
  validate(): Promise<boolean>;
131
- setErrors(_errors: ValidationError[]): void;
129
+ /**
130
+ * Установить array-level validation errors
131
+ *
132
+ * @param errors - Массив ошибок валидации уровня массива
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * arrayNode.setErrors([{
137
+ * code: 'minItems',
138
+ * message: 'Минимум 1 элемент обязателен',
139
+ * }]);
140
+ * ```
141
+ */
142
+ setErrors(errors: ValidationError[]): void;
143
+ /**
144
+ * Очистить все errors (array-level + item-level)
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * arrayNode.clearErrors();
149
+ * console.log(arrayNode.errors.value); // []
150
+ * ```
151
+ */
132
152
  clearErrors(): void;
133
153
  /**
134
154
  * Hook: вызывается после markAsTouched()
@@ -156,15 +176,15 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
156
176
  protected onMarkAsPristine(): void;
157
177
  /**
158
178
  * Итерировать по элементам массива
159
- * @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode
179
+ * @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode proxy
160
180
  */
161
- forEach(callback: (item: GroupNodeWithControls<T>, index: number) => void): void;
181
+ forEach(callback: (item: FormProxy<T>, index: number) => void): void;
162
182
  /**
163
183
  * Маппинг элементов массива
164
- * @param callback - Функция преобразования с типизированным GroupNode
184
+ * @param callback - Функция преобразования с типизированным GroupNode proxy
165
185
  * @returns Новый массив результатов
166
186
  */
167
- map<R>(callback: (item: GroupNodeWithControls<T>, index: number) => R): R[];
187
+ map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];
168
188
  /**
169
189
  * Создать новый элемент массива на основе схемы
170
190
  * @param initialValue - Начальные значения
@@ -1,15 +1,6 @@
1
- /**
2
- * FieldNode - узел поля формы
3
- *
4
- * Представляет одно поле формы с валидацией и состоянием
5
- * Наследует от FormNode и реализует все его абстрактные методы
6
- *
7
- * @group Nodes
8
- */
9
- import type { ReadonlySignal } from '@preact/signals-core';
10
- import { FormNode } from './form-node';
11
- import type { SetValueOptions } from './form-node';
12
- import type { FieldConfig, ValidationError } from '../types';
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { FormNode, SetValueOptions } from './form-node';
3
+ import { FieldConfig, FieldStatus, ValidationError } from '../types';
13
4
  /**
14
5
  * FieldNode - узел для отдельного поля формы
15
6
  *
@@ -31,12 +22,18 @@ import type { FieldConfig, ValidationError } from '../types';
31
22
  export declare class FieldNode<T> extends FormNode<T> {
32
23
  private _value;
33
24
  private _errors;
34
- private _pending;
35
25
  private _componentProps;
26
+ /**
27
+ * State machine для управления статусом поля
28
+ * Централизует логику переходов между valid/invalid/pending/disabled
29
+ */
30
+ private readonly statusMachine;
36
31
  readonly value: ReadonlySignal<T>;
37
32
  readonly valid: ReadonlySignal<boolean>;
38
33
  readonly invalid: ReadonlySignal<boolean>;
39
34
  readonly pending: ReadonlySignal<boolean>;
35
+ readonly status: ReadonlySignal<FieldStatus>;
36
+ readonly disabled: ReadonlySignal<boolean>;
40
37
  readonly errors: ReadonlySignal<ValidationError[]>;
41
38
  readonly componentProps: ReadonlySignal<Record<string, any>>;
42
39
  /**
@@ -48,10 +45,14 @@ export declare class FieldNode<T> extends FormNode<T> {
48
45
  private asyncValidators;
49
46
  private updateOn;
50
47
  private initialValue;
51
- private currentValidationId;
48
+ private currentAbortController?;
52
49
  private debounceMs;
53
50
  private validateDebounceTimer?;
54
- private validateDebounceResolve?;
51
+ /**
52
+ * Pending debounced validation state
53
+ * Contains resolve function and AbortController for cancellation
54
+ */
55
+ private pendingValidation?;
55
56
  /**
56
57
  * Менеджер подписок для централизованного cleanup
57
58
  * Использует SubscriptionManager вместо массива для управления подписками
@@ -112,13 +113,23 @@ export declare class FieldNode<T> extends FormNode<T> {
112
113
  * ```
113
114
  */
114
115
  resetToInitial(): void;
116
+ /**
117
+ * Cancel any pending validation (debounced or running)
118
+ * @private
119
+ * @remarks
120
+ * Centralizes all cancellation logic:
121
+ * - Aborts pending debounced validation and resolves its promise
122
+ * - Clears debounce timer
123
+ * - Aborts currently running async validation
124
+ */
125
+ private cancelPendingValidation;
115
126
  /**
116
127
  * Запустить валидацию поля
117
128
  * @param options - опции валидации
118
129
  * @returns `Promise<boolean>` - true если поле валидно
119
130
  *
120
131
  * @remarks
121
- * Метод защищен от race conditions через validationId.
132
+ * Метод защищен от race conditions через AbortController.
122
133
  * При быстром вводе только последняя валидация применяет результаты.
123
134
  *
124
135
  * @example
@@ -136,13 +147,12 @@ export declare class FieldNode<T> extends FormNode<T> {
136
147
  /**
137
148
  * Немедленная валидация без debounce
138
149
  * @private
150
+ * @param providedController - AbortController from debounced validate()
139
151
  * @remarks
140
- * Защищена от race conditions:
141
- * - Проверка validationId после синхронной валидации
142
- * - Проверка перед установкой pending
143
- * - Проверка после Promise.all
144
- * - Проверка перед обработкой async результатов
145
- * - Проверка перед очисткой errors
152
+ * Защищена от race conditions через AbortController:
153
+ * - Отменяет предыдущую валидацию при запуске новой (if no controller provided)
154
+ * - Передаёт AbortSignal в async валидаторы для отмены операций (например, fetch)
155
+ * - Проверяет signal.aborted в ключевых точках
146
156
  */
147
157
  private validateImmediate;
148
158
  setErrors(errors: ValidationError[]): void;
@@ -156,13 +166,13 @@ export declare class FieldNode<T> extends FormNode<T> {
156
166
  /**
157
167
  * Hook: вызывается после disable()
158
168
  *
159
- * Для FieldNode: очищаем ошибки валидации
169
+ * Для FieldNode: синхронизируем statusMachine и очищаем ошибки
160
170
  */
161
171
  protected onDisable(): void;
162
172
  /**
163
173
  * Hook: вызывается после enable()
164
174
  *
165
- * Для FieldNode: запускаем валидацию
175
+ * Для FieldNode: синхронизируем statusMachine и запускаем валидацию
166
176
  */
167
177
  protected onEnable(): void;
168
178
  /**
@@ -215,20 +225,28 @@ export declare class FieldNode<T> extends FormNode<T> {
215
225
  * Подписка на изменения значения поля
216
226
  * Автоматически отслеживает изменения через @preact/signals effect
217
227
  *
218
- * @param callback - Функция, вызываемая при изменении значения
228
+ * @param callback - Функция, вызываемая при изменении значения.
229
+ * Для async операций передается AbortSignal во втором параметре.
219
230
  * @returns Функция отписки для cleanup
220
231
  *
221
232
  * @example
222
233
  * ```typescript
234
+ * // Синхронный callback
223
235
  * const unsubscribe = form.email.watch((value) => {
224
236
  * console.log('Email changed:', value);
225
237
  * });
226
238
  *
239
+ * // Асинхронный callback с поддержкой отмены
240
+ * const unsubscribe = form.email.watch(async (value, signal) => {
241
+ * const result = await fetch('/api/validate', { signal });
242
+ * // ...
243
+ * });
244
+ *
227
245
  * // Cleanup
228
246
  * useEffect(() => unsubscribe, []);
229
247
  * ```
230
248
  */
231
- watch(callback: (value: T) => void | Promise<void>): () => void;
249
+ watch(callback: (value: T, signal: AbortSignal) => void | Promise<void>): () => void;
232
250
  /**
233
251
  * Вычисляемое значение из других полей
234
252
  * Автоматически обновляет текущее поле при изменении источников
@@ -256,6 +274,13 @@ export declare class FieldNode<T> extends FormNode<T> {
256
274
  * Очистить все ресурсы и таймеры
257
275
  * Должен вызываться при unmount компонента
258
276
  *
277
+ * @remarks
278
+ * Освобождает все ресурсы:
279
+ * - Отписывает все subscriptions через SubscriptionManager
280
+ * - Отменяет pending/running валидации через cancelPendingValidation()
281
+ *
282
+ * Использует try-finally для гарантированного cleanup даже при ошибках.
283
+ *
259
284
  * @example
260
285
  * ```typescript
261
286
  * useEffect(() => {