@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9

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 (224) hide show
  1. package/README.md +91 -73
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +370 -242
  4. package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +28 -7
  9. package/dist/form/behaviors/context.d.ts +19 -2
  10. package/dist/form/behaviors/index.d.ts +3 -3
  11. package/dist/form/behaviors/internals.d.ts +9 -2
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +6 -4
  15. package/dist/form/behaviors/types.d.ts +12 -4
  16. package/dist/form/create-form.d.ts +47 -35
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +45 -28
  19. package/dist/form/form-config-base.d.ts +46 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +16 -21
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +30 -14
  37. package/dist/form/signal-node-registry.d.ts +6 -7
  38. package/dist/form/submission.d.ts +93 -0
  39. package/dist/form/type-guards.d.ts +14 -73
  40. package/dist/form/types/contracts.d.ts +19 -92
  41. package/dist/form/types/field-config.d.ts +27 -0
  42. package/dist/form/types/index.d.ts +5 -57
  43. package/dist/form/types/nodes.d.ts +159 -0
  44. package/dist/form/types/schema-node.d.ts +139 -63
  45. package/dist/form/types/validation-schema.d.ts +23 -27
  46. package/dist/form/validation/context.d.ts +42 -7
  47. package/dist/form/validation/index.d.ts +9 -8
  48. package/dist/form/validation/operators.d.ts +22 -30
  49. package/dist/form/validation/run.d.ts +93 -18
  50. package/dist/form/validation/strategy.d.ts +41 -13
  51. package/dist/form/validation/types.d.ts +74 -20
  52. package/dist/form/validators/date-utils.d.ts +13 -1
  53. package/dist/form/validators/email.d.ts +8 -7
  54. package/dist/form/validators/file-type.d.ts +8 -7
  55. package/dist/form/validators/file-utils.d.ts +2 -1
  56. package/dist/form/validators/future-date.d.ts +8 -7
  57. package/dist/form/validators/index.d.ts +1 -1
  58. package/dist/form/validators/integer.d.ts +8 -7
  59. package/dist/form/validators/is-date.d.ts +11 -9
  60. package/dist/form/validators/is-number.d.ts +11 -9
  61. package/dist/form/validators/max-age.d.ts +13 -9
  62. package/dist/form/validators/max-date.d.ts +8 -7
  63. package/dist/form/validators/max-file-size.d.ts +8 -7
  64. package/dist/form/validators/max-files.d.ts +8 -7
  65. package/dist/form/validators/max-length.d.ts +14 -7
  66. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  67. package/dist/form/validators/max.d.ts +10 -8
  68. package/dist/form/validators/min-age.d.ts +12 -9
  69. package/dist/form/validators/min-date.d.ts +8 -7
  70. package/dist/form/validators/min-file-size.d.ts +8 -7
  71. package/dist/form/validators/min-files.d.ts +11 -9
  72. package/dist/form/validators/min-length.d.ts +14 -7
  73. package/dist/form/validators/min.d.ts +10 -8
  74. package/dist/form/validators/multiple-of.d.ts +10 -8
  75. package/dist/form/validators/non-negative.d.ts +8 -7
  76. package/dist/form/validators/non-zero.d.ts +8 -7
  77. package/dist/form/validators/past-date.d.ts +8 -7
  78. package/dist/form/validators/pattern.d.ts +9 -8
  79. package/dist/form/validators/phone.d.ts +8 -7
  80. package/dist/form/validators/required.d.ts +11 -9
  81. package/dist/form/validators/url.d.ts +20 -13
  82. package/dist/form/validators/validation-issue.d.ts +8 -0
  83. package/dist/i18n/descriptor.d.ts +1 -1
  84. package/dist/i18n/en.json.d.ts +5 -1
  85. package/dist/i18n/locale-store.d.ts +7 -0
  86. package/dist/i18n/message-format.d.ts +24 -4
  87. package/dist/i18n/ru.json.d.ts +5 -1
  88. package/dist/i18n/translator.d.ts +11 -0
  89. package/dist/i18n/validation-message.d.ts +42 -7
  90. package/dist/i18n.d.ts +7 -12
  91. package/dist/i18n.js +66 -283
  92. package/dist/index.d.ts +16 -7
  93. package/dist/index.js +924 -2855
  94. package/dist/loader-BWkigpVe.js +52 -0
  95. package/dist/locale/en.js +1 -1
  96. package/dist/locale/en.json +4 -1
  97. package/dist/locale/ru.js +4 -1
  98. package/dist/locale/ru.json +4 -1
  99. package/dist/locale-CG54AU2Q.js +219 -0
  100. package/dist/locale.js +1 -1
  101. package/dist/model/behaviors-value.d.ts +24 -6
  102. package/dist/model/create-model.d.ts +2 -2
  103. package/dist/model/derived-registry.d.ts +1 -1
  104. package/dist/model/index.d.ts +11 -5
  105. package/dist/model/model-nodes.d.ts +79 -12
  106. package/dist/model/model-signals-proxy.d.ts +5 -6
  107. package/dist/model/model-value-proxy.d.ts +20 -15
  108. package/dist/model/safe-effect.d.ts +33 -1
  109. package/dist/model/types.d.ts +63 -13
  110. package/dist/model.js +20 -18
  111. package/dist/node-kind-C-r6ZMdo.js +15 -0
  112. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  113. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  114. package/dist/platforms/react/form-bundle-context.d.ts +2 -2
  115. package/dist/platforms/react/hooks/types.d.ts +57 -38
  116. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  117. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  118. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  119. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  120. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  121. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  122. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  123. package/dist/platforms/react/i18n/context.d.ts +6 -1
  124. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  125. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  126. package/dist/platforms/react/index.d.ts +6 -4
  127. package/dist/react.d.ts +22 -0
  128. package/dist/react.js +505 -0
  129. package/dist/runtime-token.d.ts +1 -2
  130. package/dist/safe-effect-BbtX3KuN.js +643 -0
  131. package/dist/schema-controller-CpiO2JoK.js +143 -0
  132. package/dist/signals.d.ts +15 -9
  133. package/dist/signals.js +11 -6
  134. package/dist/strategy-CoFSemuP.js +375 -0
  135. package/dist/type-guards-D-N_5Ddh.js +19 -0
  136. package/dist/validation-issue-B9mjI8jz.js +7 -0
  137. package/dist/validation-message-Ced9MGxs.js +194 -0
  138. package/dist/validation.js +9 -9
  139. package/dist/validators/email.js +5 -5
  140. package/dist/validators/future-date.js +8 -8
  141. package/dist/validators/integer.js +3 -3
  142. package/dist/validators/is-date.js +4 -4
  143. package/dist/validators/is-number.js +4 -4
  144. package/dist/validators/max-age.js +8 -9
  145. package/dist/validators/max-date.js +8 -8
  146. package/dist/validators/max-length.js +7 -7
  147. package/dist/validators/max.js +4 -4
  148. package/dist/validators/min-age.js +10 -8
  149. package/dist/validators/min-date.js +3 -3
  150. package/dist/validators/min-length.js +5 -5
  151. package/dist/validators/min.js +4 -4
  152. package/dist/validators/multiple-of.js +11 -7
  153. package/dist/validators/non-negative.js +3 -3
  154. package/dist/validators/non-zero.js +3 -3
  155. package/dist/validators/past-date.js +8 -8
  156. package/dist/validators/pattern.js +5 -5
  157. package/dist/validators/phone.js +11 -9
  158. package/dist/validators/required.js +2 -2
  159. package/dist/validators/url.js +48 -12
  160. package/dist/validators.js +17 -17
  161. package/docs/llms/01-api-reference.md +60 -34
  162. package/docs/llms/02-quick-start.md +111 -80
  163. package/docs/llms/03-api-signatures.md +368 -153
  164. package/docs/llms/04-common-patterns.md +58 -56
  165. package/docs/llms/05-common-mistakes.md +69 -44
  166. package/docs/llms/06-troubleshooting.md +14 -9
  167. package/docs/llms/07-complete-import.md +74 -43
  168. package/docs/llms/08-form-types.md +11 -4
  169. package/docs/llms/09-formschema.md +129 -81
  170. package/docs/llms/10-arrays.md +106 -75
  171. package/docs/llms/11-async-watchfield.md +2 -2
  172. package/docs/llms/13-multi-step.md +134 -63
  173. package/docs/llms/14-extended-mistakes.md +20 -17
  174. package/docs/llms/15-project-structure.md +221 -74
  175. package/docs/llms/16-ui-components.md +55 -42
  176. package/docs/llms/17-nonexistent-api.md +199 -32
  177. package/docs/llms/18-conditional-fields.md +66 -32
  178. package/docs/llms/19-reading-values.md +15 -3
  179. package/docs/llms/20-compute-vs-watch.md +16 -10
  180. package/docs/llms/21-array-operations.md +51 -27
  181. package/docs/llms/22-cycle-detection.md +1 -1
  182. package/docs/llms/23-copy-from.md +2 -2
  183. package/docs/llms/24-sync-fields.md +6 -5
  184. package/docs/llms/25-reset-when.md +14 -7
  185. package/docs/llms/26-transform-value.md +4 -4
  186. package/docs/llms/27-revalidate-when.md +16 -15
  187. package/docs/llms/28-submit-and-reset.md +276 -128
  188. package/docs/llms/29-async-preload.md +39 -29
  189. package/docs/llms/30-type-safety-recipes.md +105 -51
  190. package/docs/llms/31-async-validator-debounce.md +96 -53
  191. package/docs/llms/33-validation-strategy.md +177 -65
  192. package/docs/llms/34-i18n.md +69 -43
  193. package/docs/llms/35-unified-contract.md +100 -39
  194. package/docs/llms/36-issue-messages.md +284 -0
  195. package/docs/llms/37-compiled-schema.md +332 -0
  196. package/docs/llms/38-form-flow.md +397 -0
  197. package/llms-index.json +1 -1
  198. package/llms.txt +6084 -3420
  199. package/package.json +23 -4
  200. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  201. package/dist/date-utils-dTT_x_Be.js +0 -31
  202. package/dist/form/create-core-form.d.ts +0 -72
  203. package/dist/form/factories/index.d.ts +0 -6
  204. package/dist/form/factories/node-factory.d.ts +0 -189
  205. package/dist/form/form-submitter.d.ts +0 -121
  206. package/dist/form/nodes/array-node.d.ts +0 -276
  207. package/dist/form/nodes/field-node.d.ts +0 -297
  208. package/dist/form/nodes/form-node.d.ts +0 -326
  209. package/dist/form/nodes/group-node.d.ts +0 -284
  210. package/dist/form/nodes/model-array-node.d.ts +0 -126
  211. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  212. package/dist/form/status-machine.d.ts +0 -115
  213. package/dist/form/types/deep-schema.d.ts +0 -107
  214. package/dist/form/types/form-proxy.d.ts +0 -141
  215. package/dist/form/unique-id.d.ts +0 -45
  216. package/dist/form/validation/config.d.ts +0 -73
  217. package/dist/form/validation/error-handler.d.ts +0 -166
  218. package/dist/form/validators/validation-error.d.ts +0 -8
  219. package/dist/index-BrNkQj1y.js +0 -105
  220. package/dist/loader-Dv_Hl7JC.js +0 -216
  221. package/dist/model-value-proxy-caTmAiUd.js +0 -419
  222. package/dist/node-BeV_lV5i.js +0 -100
  223. package/dist/strategy-DgzELWno.js +0 -204
  224. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,20 +1,19 @@
1
- import { FormNode } from './nodes/form-node';
1
+ import { FormNode } from './types/nodes';
2
2
  /**
3
3
  * Связать ручку модели с её нодой формы.
4
4
  *
5
5
  * Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
6
- * группы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.
6
+ * группы и массива под-форм; у группы в реестре лежит её лицо {@link FormGroup} с нодами полей.
7
+ * Прикладной код обычно этот реестр не заполняет напрямую.
7
8
  *
8
9
  * @param signal - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
9
10
  * @param node - Нода формы, отвечающая за эту часть модели
10
11
  *
11
- * @example Привязка листовых полей при построении формы
12
+ * @example Ручка модели указывает на ноду другой сборки
12
13
  * ```typescript
13
14
  * import { registerSignalNode } from '@reformer/core';
14
15
  *
15
- * const sig = model.signalAt('profile.email');
16
- * const node = group.getFieldByPath('profile.email');
17
- * if (sig && node) registerSignalNode(sig, node);
16
+ * registerSignalNode(model.$.profile.email, profileForm.email);
18
17
  * ```
19
18
  *
20
19
  * @see {@link getNodeForSignal} - обратный поиск ноды по ручке
@@ -37,7 +36,7 @@ export declare function registerSignalNode(signal: object, node: FormNode<any>):
37
36
  * import { getNodeForSignal } from '@reformer/core';
38
37
  *
39
38
  * const sig = model.$.email;
40
- * getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
39
+ * getNodeForSignal(sig)?.setIssues([{ code: 'required' }]);
41
40
  * ```
42
41
  *
43
42
  * @example Нода группы и строки массива
@@ -0,0 +1,93 @@
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { ValidationResult } from './validation/types';
3
+ /**
4
+ * Итог отправки: `submitted` — обработчик отработал, `invalid` / `error` / `cancelled` —
5
+ * проверка не пропустила (обработчик не вызывался), `failed` — обработчик бросил исключение.
6
+ *
7
+ * `cancelled` — проверку отправки сменила следующая (вторая отправка, живой прогон) либо модель
8
+ * изменилась, пока проверка шла: правила видели прежние значения, и отправлять новые нельзя.
9
+ * Замечаний такой итог не несёт — отправку повторяют.
10
+ *
11
+ * @group Form
12
+ */
13
+ export type SubmitResult<TResult> = {
14
+ readonly status: 'submitted';
15
+ readonly result: TResult;
16
+ } | {
17
+ readonly status: 'invalid' | 'error' | 'cancelled';
18
+ readonly validation: ValidationResult;
19
+ } | {
20
+ readonly status: 'failed';
21
+ readonly error: unknown;
22
+ };
23
+ /**
24
+ * Итог проверки для действия по её результату (отправка, переход шага).
25
+ *
26
+ * @group Form
27
+ */
28
+ export interface ActionCheck<T> {
29
+ /** Результат проверки. */
30
+ readonly result: ValidationResult;
31
+ /** Значения модели после проверки; при `stale === false` — ровно те, что видели правила. */
32
+ readonly values: T;
33
+ /** Проверка успешна, но модель с тех пор изменилась: её успех относится к прежним значениям. */
34
+ readonly stale: boolean;
35
+ }
36
+ /**
37
+ * Зависимости отправки. Явные, чтобы модуль не тянул ни модель, ни ноды формы.
38
+ *
39
+ * @group Form
40
+ */
41
+ export interface SubmissionDependencies<T> {
42
+ /**
43
+ * Проверка перед отправкой — попытка отправки: замечания раскрыты, при необходимости вся форма
44
+ * тронута. `undefined` — правил нет: обработчик получает {@link SubmissionDependencies.values}.
45
+ */
46
+ readonly check: (() => Promise<ActionCheck<T>>) | undefined;
47
+ /** Значения без проверки. */
48
+ readonly values: () => T;
49
+ }
50
+ /**
51
+ * Отправка формы.
52
+ *
53
+ * @group Form
54
+ */
55
+ export interface FormSubmission<T> {
56
+ /** Идёт ли отправка: работает обработчик. Пока идёт проверка, — `false`. */
57
+ readonly submitting: ReadonlySignal<boolean>;
58
+ /**
59
+ * Отправить: один прогон всех правил; при успехе — обработчик со значениями модели.
60
+ *
61
+ * Обработчик получает ровно те значения, которые видели правила. Пока идёт async-правило,
62
+ * форма не заперта (`submitting` ещё `false`); если модель за это время изменилась — правкой
63
+ * пользователя, загрузкой, записью поведения после таймера или запроса, — итог `cancelled`,
64
+ * обработчик не вызывается.
65
+ *
66
+ * Значения — модель целиком, с выключенными полями: их правила не считаются, но данные
67
+ * принадлежат модели. Убрать их из отправки — забота обработчика либо
68
+ * `enableWhen(…, { resetOnDisable: true })`.
69
+ *
70
+ * Исключение обработчика наружу не выходит: итог `failed` с этим исключением.
71
+ */
72
+ submit<TResult>(handler: (values: T) => TResult | Promise<TResult>): Promise<SubmitResult<TResult>>;
73
+ }
74
+ /**
75
+ * Создать отправку формы.
76
+ *
77
+ * Обычно её создаёт сборка (`createForm` → `bundle.submission`). Прямой вызов нужен там, где
78
+ * сборки нет: визард в разметке без `createForm` отправляет значения формы без проверки.
79
+ *
80
+ * @typeParam T - Форма данных.
81
+ * @param dependencies - {@link SubmissionDependencies}: проверка перед действием и значения.
82
+ * @returns {@link FormSubmission}.
83
+ *
84
+ * @example Без проверки — значения формы как есть
85
+ * ```typescript
86
+ * const submission = createSubmission<Profile>({ check: undefined, values: () => form.getValue() });
87
+ * const outcome = await submission.submit(api.save);
88
+ * if (outcome.status === 'failed') showToast('Не удалось сохранить');
89
+ * ```
90
+ *
91
+ * @group Form
92
+ */
93
+ export declare function createSubmission<T>(dependencies: SubmissionDependencies<T>): FormSubmission<T>;
@@ -1,97 +1,38 @@
1
- import { FormNode } from './nodes/form-node';
2
- import { FieldNode } from './nodes/field-node';
3
- import { GroupNode } from './nodes/group-node';
4
- import { ArrayNode } from './nodes/array-node';
5
- import { FormValue } from './types/index';
1
+ import { FormArray, FormControl, FormGroup, FormNode } from './types/nodes';
6
2
  /**
7
- * Проверить, является ли значение любым FormNode
8
- *
9
- * Проверяет базовые свойства, общие для всех типов узлов
10
- *
11
- * @group Utilities
12
- * @category Type Guards
13
- *
14
- * @param value - Значение для проверки
15
- * @returns true если value является FormNode
16
- *
17
- * @example
18
- * ```typescript
19
- * if (isFormNode(value)) {
20
- * value.setValue(newValue);
21
- * value.validate();
22
- * }
23
- * ```
24
- */
25
- export declare function isFormNode(value: unknown): value is FormNode<FormValue>;
26
- /**
27
- * Проверить, является ли значение FieldNode (примитивное поле)
28
- *
29
- * FieldNode представляет примитивное поле формы (string, number, boolean и т.д.)
30
- * и имеет валидаторы, но не имеет вложенных полей или элементов массива
31
- *
32
- * @group Utilities
33
- * @category Type Guards
34
- *
35
- * @param value - Значение для проверки
36
- * @returns true если value является FieldNode
3
+ * Нода формы любого вида — поле, группа или массив под-форм.
37
4
  *
38
5
  * @example
39
6
  * ```typescript
40
- * if (isFieldNode(node)) {
41
- * node.validators; // OK
42
- * node.asyncValidators; // OK
43
- * node.markAsTouched(); // OK
44
- * }
7
+ * const node = getNodeForSignal(model.$.email);
8
+ * if (isFormNode(node)) node.markAsTouched();
45
9
  * ```
46
10
  */
47
- export declare function isFieldNode(value: unknown): value is FieldNode<FormValue>;
11
+ export declare function isFormNode(value: unknown): value is FormNode<unknown>;
48
12
  /**
49
- * Проверить, является ли значение GroupNode (объект с вложенными полями)
50
- *
51
- * GroupNode представляет объект с вложенными полями формы: имеет навигацию по полям
52
- * (`getFieldByPath`/`fields`) и НЕ имеет array-методов (`items`/`push`/`removeAt`).
53
- *
54
- * @param value - Значение для проверки
55
- * @returns true если value является GroupNode
13
+ * Поле формы — нода над листом модели с пропсами компонента.
56
14
  *
57
15
  * @example
58
16
  * ```typescript
59
- * if (isGroupNode(node)) {
60
- * node.getFieldByPath('user.email'); // OK
61
- * }
17
+ * if (isFormControl(node)) node.updateComponentProps({ placeholder: 'Email' });
62
18
  * ```
63
19
  */
64
- export declare function isGroupNode(value: unknown): value is GroupNode<object>;
20
+ export declare function isFormControl(value: unknown): value is FormControl<unknown>;
65
21
  /**
66
- * Проверить, является ли значение ArrayNode (массив форм)
67
- *
68
- * ArrayNode представляет массив вложенных форм (обычно GroupNode)
69
- * и имеет array-like методы (push, removeAt, at)
70
- *
71
- * @param value - Значение для проверки
72
- * @returns true если value является ArrayNode
22
+ * Группа полей — её прокси с нодами полей по именам.
73
23
  *
74
24
  * @example
75
25
  * ```typescript
76
- * if (isArrayNode(node)) {
77
- * node.push(); // OK - добавить элемент
78
- * node.removeAt(0); // OK - удалить элемент
79
- * const item = node.at(0); // OK - получить элемент
80
- * }
26
+ * if (isFormGroup(node)) node.markAsTouched();
81
27
  * ```
82
28
  */
83
- export declare function isArrayNode(value: unknown): value is ArrayNode<object>;
29
+ export declare function isFormGroup(value: unknown): value is FormGroup<Record<string, unknown>>;
84
30
  /**
85
- * Получить тип узла как строку (для отладки)
86
- *
87
- * Полезно для логирования и отладки
88
- *
89
- * @param node - Узел для проверки
90
- * @returns Строковое название типа узла
31
+ * Массив под-форм.
91
32
  *
92
33
  * @example
93
34
  * ```typescript
94
- * console.log('Node type:', getNodeType(node)); // "FieldNode" | "GroupNode" | "ArrayNode" | "FormNode" | "Unknown"
35
+ * if (isFormArray(node)) node.push();
95
36
  * ```
96
37
  */
97
- export declare function getNodeType(node: unknown): string;
38
+ export declare function isFormArray(value: unknown): value is FormArray<object>;
@@ -1,109 +1,36 @@
1
1
  /**
2
- * Общие контракт-типы значения и валидации.
2
+ * Контракт замечания валидации.
3
3
  *
4
- * Базовый словарь «значение + ошибка + статус», на который опираются узлы формы
5
- * (`FormNode.getErrors`, `FieldNode`, `FormStatusMachine`, `aggregate-signals`), слой валидации
6
- * и платформенные биндинги. Файл не импортирует ничего — это лист графа зависимостей.
4
+ * Базовый словарь, на который опираются ноды формы, слой валидации и платформенные биндинги.
5
+ * Файл не импортирует ничего — это лист графа зависимостей.
7
6
  *
8
- * Живёт в `form/types/`, а не в слое модели: `FieldStatus`/`ErrorFilterOptions` — понятия формы,
9
- * а модель этих типов не использует (и импортировать их оттуда запрещено границей model⇏form).
7
+ * Живёт в `form/types/`, а не в слое модели: замечание — понятие формы, а модель этих типов
8
+ * не использует (и импортировать их оттуда запрещено границей model⇏form).
10
9
  * Наружу отдаётся через `form/types/index`.
11
10
  *
12
11
  * @group Types
13
12
  * @module form/types/contracts
14
13
  */
15
14
  /**
16
- * Represents any valid form value type
17
- * Use this instead of 'any' for form values to maintain type safety
18
- *
19
- * @group Types
20
- * @category Core Types
21
- */
22
- export type FormValue = string | number | boolean | null | undefined | Date | File | FormValue[] | {
23
- [key: string]: FormValue;
24
- };
25
- /**
26
- * Синхронная функция валидации
27
- * @group Types
28
- * @category Validation Types
29
- */
30
- export type ValidatorFn<T = FormValue> = (value: T) => ValidationError | null;
31
- /**
32
- * Опции для асинхронного валидатора
33
- * @group Types
34
- * @category Validation Types
35
- */
36
- export interface AsyncValidatorOptions {
37
- /**
38
- * AbortSignal для отмены валидации
39
- * Позволяет отменить асинхронную операцию при новой валидации
40
- */
41
- signal?: AbortSignal;
42
- }
43
- /**
44
- * Асинхронная функция валидации
45
- *
46
- * @param value - Значение для валидации
47
- * @param options - Опции валидации (опционально)
48
- * @returns Promise с ошибкой валидации или null если значение валидно
15
+ * Замечание валидации: что нарушено (`code`) и с какими числами (`params`). Текста у замечания
16
+ * нет — отображаемую строку по коду даёт слой представления (`resolveIssueMessage` из
17
+ * `@reformer/core/i18n`): словарь формы → словарь локали → встроенный английский → сам код.
18
+ * Поэтому одно и то же замечание показывается на любом языке без повторной проверки.
49
19
  *
50
20
  * @example
51
- * ```typescript
52
- * // Простой валидатор (без поддержки отмены)
53
- * const emailExists: AsyncValidatorFn<string> = async (value) => {
54
- * const exists = await checkEmail(value);
55
- * return exists ? { code: 'exists', message: 'Email already exists' } : null;
56
- * };
57
- *
58
- * // Валидатор с поддержкой отмены
59
- * const emailExistsAbortable: AsyncValidatorFn<string> = async (value, options) => {
60
- * const exists = await fetch(`/api/check-email?email=${value}`, {
61
- * signal: options?.signal // Передаём signal в fetch для отмены запроса
62
- * });
63
- * return exists ? { code: 'exists', message: 'Email already exists' } : null;
64
- * };
21
+ * ```ts
22
+ * const tooShort: ValidationIssue = { code: 'minLength', params: { minLength: 8 } };
23
+ * const softLimit: ValidationIssue = { code: 'loanAmount.high', severity: 'warning' };
65
24
  * ```
66
25
  *
67
26
  * @group Types
68
27
  * @category Validation Types
69
28
  */
70
- export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidatorOptions) => Promise<ValidationError | null>;
71
- /**
72
- * Ошибка валидации
73
- * @group Types
74
- * @category Validation Types
75
- */
76
- export interface ValidationError {
77
- code: string;
78
- /**
79
- * Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку
80
- * тогда даёт резолвер по `code` (словарь локали `validation.<code>`).
81
- */
82
- message: string;
83
- /** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */
84
- messageKey?: string;
85
- params?: Record<string, FormValue>;
86
- /** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
87
- severity?: 'error' | 'warning';
29
+ export interface ValidationIssue {
30
+ /** Код правила: встроенный (`required`, `minLength`) либо свой (`loanAmount.required`). */
31
+ readonly code: string;
32
+ /** Значения для подстановки в текст: порог правила, фактическое значение. */
33
+ readonly params?: Readonly<Record<string, unknown>>;
34
+ /** `'error'` (по умолчанию) блокирует отправку, `'warning'` показывается, но не блокирует. */
35
+ readonly severity?: 'error' | 'warning';
88
36
  }
89
- /**
90
- * Опции для фильтрации ошибок в методе getErrors()
91
- * @group Types
92
- * @category Validation Types
93
- */
94
- export interface ErrorFilterOptions {
95
- /** Фильтр по коду ошибки */
96
- code?: string | string[];
97
- /** Фильтр по сообщению (поддерживает частичное совпадение) */
98
- message?: string;
99
- /** Фильтр по параметрам ошибки */
100
- params?: Record<string, FormValue>;
101
- /** Кастомный предикат для фильтрации */
102
- predicate?: (error: ValidationError) => boolean;
103
- }
104
- /**
105
- * Статус поля формы
106
- * @group Types
107
- * @category Core Types
108
- */
109
- export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
@@ -0,0 +1,27 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ import { ComponentRef } from './schema-node';
3
+ /**
4
+ * Конфигурация поля формы ({@link FormControl}): сигнал значения из модели и то, что нода берёт
5
+ * из узла схемы.
6
+ *
7
+ * В схеме формы поле записывают узлом `{ model: model.$.field, component, componentProps }`;
8
+ * сборка `createFormFromModel` превращает узел в этот конфиг сама. Правил валидации в конфиге нет —
9
+ * они живут в схеме валидации (`defineValidation`, `@reformer/core/validation`).
10
+ *
11
+ * @group Types
12
+ * @category Configuration Types
13
+ */
14
+ export interface FieldConfig<T> {
15
+ /**
16
+ * Сигнал значения из {@link FormModel} — источник истины значения поля. Нода значением не
17
+ * владеет, а ссылается на этот сигнал.
18
+ */
19
+ valueSignal: Signal<T>;
20
+ /**
21
+ * UI-компонент поля. Опционален: core-часть можно использовать без ссылки на компонент
22
+ * (значение и состояние работают без UI; компонент нужен только для рендеринга).
23
+ */
24
+ component?: ComponentRef;
25
+ componentProps?: any;
26
+ disabled?: boolean;
27
+ }
@@ -1,57 +1,5 @@
1
- import { FormSchema } from './deep-schema';
2
- import { FormNode } from '../nodes/form-node';
3
- export type { FormValue, ValidatorFn, AsyncValidatorOptions, AsyncValidatorFn, ValidationError, ErrorFilterOptions, FieldStatus, } from './contracts';
4
- export type { FieldConfig } from './deep-schema';
5
- export type { Validator, ValidateOptions } from './validation-schema';
6
- export type { FormSchema } from './deep-schema';
7
- export type { FormSchemaNode, SchemaArrayControl } from './schema-node';
8
- export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
9
- /**
10
- * Конфигурация GroupNode.
11
- *
12
- * Под M1 группа создаётся из плоской {@link FormSchema} (дерево field-конфигов). Обёртка
13
- * `{ form }` сохранена для совместимости вызова, legacy behavior/validation-схемы удалены (Ф7).
14
- *
15
- * @group Types
16
- * @category Configuration Types
17
- */
18
- export interface GroupNodeConfig<T> {
19
- /** Схема структуры формы (поля и их конфигурация) */
20
- form: FormSchema<T>;
21
- }
22
- /**
23
- * Тип для Record с unknown значениями
24
- * Используется вместо инлайнового `Record<string, unknown>`
25
- * @internal
26
- */
27
- export type UnknownRecord = Record<string, unknown>;
28
- /**
29
- * Интерфейс для узлов, похожих на ArrayNode (с методом at)
30
- * Используется для duck typing при обходе путей
31
- * @internal
32
- */
33
- export interface ArrayNodeLike {
34
- at(index: number): FormNode<unknown> | undefined;
35
- length: unknown;
36
- }
37
- /**
38
- * Конфиг с полем schema (для ArrayConfig)
39
- * @internal
40
- */
41
- export interface ConfigWithSchema {
42
- schema: unknown;
43
- initialItems?: unknown[];
44
- }
45
- /**
46
- * Конфиг с полем value (для извлечения значений)
47
- * @internal
48
- */
49
- export interface ConfigWithValue {
50
- value: unknown;
51
- }
52
- /**
53
- * Тип для проверки на функцию в conditional types
54
- * Используется вместо Function для type narrowing
55
- * @internal
56
- */
57
- export type AnyFunction = (...args: never[]) => unknown;
1
+ export type { ValidationIssue } from './contracts';
2
+ export type { FieldConfig } from './field-config';
3
+ export type { RuleOptions } from './validation-schema';
4
+ export type { ComponentRef, FormSchemaNode, SchemaFieldNode, SchemaArrayNode, SchemaPartNode, SchemaContainerNode, SchemaChild, SchemaValueHandle, SchemaArrayHandle, SchemaGroupHandle, SchemaArrayControl, } from './schema-node';
5
+ export type { FormNode, FormControl, FormGroup, FormArray, FormFields } from './nodes';
@@ -0,0 +1,159 @@
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { ValidationIssue } from './contracts';
3
+ import { ComponentRef } from './schema-node';
4
+ /**
5
+ * Общий контракт ноды формы — поля, группы и массива под-форм.
6
+ *
7
+ * Значением нода не владеет: источник истины — модель (`model.$.<путь>`), нода держит состояние
8
+ * взаимодействия (`touched`, `dirty`), доступность и замечания валидации.
9
+ *
10
+ * @typeParam T - Значение ноды
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const { form } = createForm<{ email: string }>({ initial: { email: '' } });
15
+ * form.email.setValue('user@mail.com');
16
+ * form.email.markAsTouched();
17
+ * form.valid.value; // true — замечаний нет
18
+ * ```
19
+ */
20
+ export interface FormNode<T> {
21
+ /** Текущее значение. */
22
+ readonly value: ReadonlySignal<T>;
23
+ /** Замечаний, которые блокируют отправку, нет; нода включена и не ждёт проверки. */
24
+ readonly valid: ReadonlySignal<boolean>;
25
+ /** Есть блокирующие замечания; нода включена и не ждёт проверки. */
26
+ readonly invalid: ReadonlySignal<boolean>;
27
+ /** Идёт асинхронная проверка. */
28
+ readonly pending: ReadonlySignal<boolean>;
29
+ /** Нода отключена: её замечания сняты, в проверку она не входит. */
30
+ readonly disabled: ReadonlySignal<boolean>;
31
+ /** Пользователь взаимодействовал с нодой. */
32
+ readonly touched: ReadonlySignal<boolean>;
33
+ /** Значение меняли после создания или сброса. */
34
+ readonly dirty: ReadonlySignal<boolean>;
35
+ /** Замечания ноды; у группы и массива — свои и всех детей. */
36
+ readonly issues: ReadonlySignal<readonly ValidationIssue[]>;
37
+ /**
38
+ * Собственные замечания ноды — без замечаний детей: у группы и массива это правила на их ручке
39
+ * (`minLength` массива, `cross` группы), у поля — то же, что `issues`.
40
+ */
41
+ readonly ownIssues: ReadonlySignal<readonly ValidationIssue[]>;
42
+ /** Значение без подписки. */
43
+ getValue(): T;
44
+ /** Записать значение в модель. */
45
+ setValue(value: T): void;
46
+ /**
47
+ * Вернуть значение и состояние к начальным; с аргументом — к переданному значению. В цель
48
+ * `compute` значение не пишется (кроме явного `reset(значение)` самого поля): ею владеет
49
+ * поведение — сбрасывается только состояние.
50
+ */
51
+ reset(value?: T): void;
52
+ markAsTouched(): void;
53
+ markAsUntouched(): void;
54
+ enable(): void;
55
+ disable(): void;
56
+ /** Поставить замечания извне (ответ сервера, своя проверка). */
57
+ setIssues(issues: readonly ValidationIssue[]): void;
58
+ clearIssues(): void;
59
+ }
60
+ /**
61
+ * Поле формы: нода над листом модели и состояние его компонента.
62
+ *
63
+ * @typeParam T - Значение поля
64
+ */
65
+ export interface FormControl<T> extends FormNode<T> {
66
+ /** Компонент поля из узла схемы. */
67
+ readonly component: ComponentRef | undefined;
68
+ /** Пропсы компонента: из узла схемы и правок {@link FormControl.updateComponentProps}. */
69
+ readonly componentProps: ReadonlySignal<Readonly<Record<string, unknown>>>;
70
+ /**
71
+ * Показывать ли состояние ошибки (`aria-invalid`, рамка): поле невалидно и его трогали или
72
+ * меняли. Предупреждение (`severity: 'warning'`) поле невалидным не делает — тексты замечаний
73
+ * показывают по {@link FormControl.shouldShowIssues}.
74
+ */
75
+ readonly shouldShowError: ReadonlySignal<boolean>;
76
+ /**
77
+ * Показывать ли тексты замечаний: у поля есть замечания — ошибки или предупреждения, — и его
78
+ * трогали или меняли. Условия те же, что у {@link FormControl.shouldShowError} (не выключено,
79
+ * проверка не идёт), но в счёт идёт любое замечание, а не только блокирующее.
80
+ */
81
+ readonly shouldShowIssues: ReadonlySignal<boolean>;
82
+ /** Слить правку в пропсы компонента (например, опции после загрузки справочника). */
83
+ updateComponentProps(patch: Record<string, unknown>): void;
84
+ }
85
+ /**
86
+ * Массив под-форм: строка — {@link FormGroup} над элементом массива модели.
87
+ *
88
+ * Данными массив не владеет: мутации уходят в массив модели, формы строк держатся за под-модели
89
+ * и при `move`/`swap` не пересоздаются.
90
+ *
91
+ * Индексы (`at`, `keyAt`, `insert`, `removeAt`, `move`, `swap`) — по строкам формы. Элемент `null`
92
+ * в массиве модели строкой не становится, и индекс строки может не совпасть с индексом модели.
93
+ *
94
+ * @typeParam T - Элемент массива
95
+ */
96
+ export interface FormArray<T extends object> extends FormNode<T[]> {
97
+ /** Число строк; чтение в эффекте или `computed` подписывает на изменения. */
98
+ readonly length: number;
99
+ at(index: number): FormGroup<T> | undefined;
100
+ map<R>(callback: (row: FormGroup<T>, index: number) => R): R[];
101
+ forEach(callback: (row: FormGroup<T>, index: number) => void): void;
102
+ /** Стабильный ключ строки: держится за элемент модели и не меняется при `move` и `swap`. */
103
+ keyAt(index: number): string;
104
+ /** Добавить строку; без значения — по шаблону массива модели (`arrayOf(blank)`). */
105
+ push(item?: T): void;
106
+ insert(index: number, item?: T): void;
107
+ removeAt(index: number): void;
108
+ move(from: number, to: number): void;
109
+ swap(first: number, second: number): void;
110
+ clear(): void;
111
+ }
112
+ /**
113
+ * Признак «объект-группа»: обычный объект, а не массив, дата, файл, функция или примитив.
114
+ * `extends object` ловит и `interface`, и `type` (интерфейс не приводится к `Record`).
115
+ * Значение неизвестной формы (`unknown`, `{}`) — поле: у него нет известных полей, и словарь
116
+ * `Record<string, unknown>` остаётся группой полей, а не группой групп — иначе такая группа не
117
+ * приводилась бы к `FormGroup<any>` внутри обобщённых типов (контекст React, элементы списка).
118
+ *
119
+ * @internal
120
+ */
121
+ type IsGroupObject<V> = V extends ReadonlyArray<unknown> ? false : V extends Date | File | Blob ? false : V extends (...args: never[]) => unknown ? false : V extends object ? Record<never, never> extends V ? false : true : false;
122
+ /** `T` — это `any`. @internal */
123
+ type IsAny<T> = 0 extends 1 & T ? true : false;
124
+ /**
125
+ * Ноды полей группы по именам полей данных: массив объектов — {@link FormArray}, объект —
126
+ * {@link FormGroup}, остальное (примитивы, массивы примитивов, даты, файлы) — {@link FormControl}.
127
+ * Опциональность снята: нода есть у каждого поля, опционально лишь значение.
128
+ *
129
+ * `FormFields<any>` — нестрогий словарь: так группа любой формы приводится к `FormGroup<any>`
130
+ * (контекст React, обобщённые компоненты), а не упирается в отображение полей типа `any`.
131
+ *
132
+ * @typeParam T - Значение группы
133
+ */
134
+ export type FormFields<T> = IsAny<T> extends true ? Record<string, any> : {
135
+ [K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<infer U> ? IsGroupObject<U> extends true ? FormArray<U & object> : FormControl<T[K]> : IsGroupObject<NonNullable<T[K]>> extends true ? FormGroup<NonNullable<T[K]>> : FormControl<T[K]>;
136
+ };
137
+ /**
138
+ * Группа полей: нода над группой модели и доступ к нодам полей.
139
+ *
140
+ * `form.email` — нода поля; поле с именем члена {@link FormNode} (`value`, `issues`, `reset`, …)
141
+ * недостижимо точкой и доступно через `form.$`: `form.$.value`. Остальные имена полей данных
142
+ * (`id`, `status`, `fields`, `submit`, …) ничем не затенены.
143
+ *
144
+ * @typeParam T - Значение группы
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * interface Order { status: string; value: number; customer: { name: string } }
149
+ * const { form } = createForm<Order>({ initial: { status: 'new', value: 0, customer: { name: '' } } });
150
+ * form.status.setValue('paid'); // поле данных
151
+ * form.$.value.setValue(100); // поле, затенённое членом ноды
152
+ * form.customer.name.markAsTouched();
153
+ * ```
154
+ */
155
+ export type FormGroup<T> = FormNode<T> & Omit<FormFields<T>, keyof FormNode<T>> & {
156
+ /** Ноды всех полей по имени, включая затенённые членами {@link FormNode}. */
157
+ readonly $: FormFields<T>;
158
+ };
159
+ export {};