@reformer/core 11.0.0-beta.7 → 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 (226) hide show
  1. package/README.md +90 -68
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +382 -217
  4. package/dist/create-model-BvcbNPgL.js +30 -0
  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 +42 -8
  9. package/dist/form/behaviors/context.d.ts +37 -4
  10. package/dist/form/behaviors/index.d.ts +6 -4
  11. package/dist/form/behaviors/internals.d.ts +9 -3
  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 +78 -0
  15. package/dist/form/behaviors/types.d.ts +25 -5
  16. package/dist/form/create-form.d.ts +57 -70
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +153 -0
  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 +20 -20
  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 +153 -0
  37. package/dist/form/schema-subtree.d.ts +28 -0
  38. package/dist/form/signal-node-registry.d.ts +27 -22
  39. package/dist/form/submission.d.ts +93 -0
  40. package/dist/form/type-guards.d.ts +14 -73
  41. package/dist/form/types/contracts.d.ts +19 -92
  42. package/dist/form/types/field-config.d.ts +27 -0
  43. package/dist/form/types/index.d.ts +5 -57
  44. package/dist/form/types/nodes.d.ts +159 -0
  45. package/dist/form/types/schema-node.d.ts +147 -46
  46. package/dist/form/types/validation-schema.d.ts +23 -27
  47. package/dist/form/validation/context.d.ts +46 -6
  48. package/dist/form/validation/index.d.ts +9 -8
  49. package/dist/form/validation/operators.d.ts +71 -17
  50. package/dist/form/validation/run.d.ts +93 -18
  51. package/dist/form/validation/strategy.d.ts +41 -13
  52. package/dist/form/validation/types.d.ts +74 -19
  53. package/dist/form/validators/date-utils.d.ts +13 -1
  54. package/dist/form/validators/email.d.ts +8 -7
  55. package/dist/form/validators/file-type.d.ts +8 -7
  56. package/dist/form/validators/file-utils.d.ts +2 -1
  57. package/dist/form/validators/future-date.d.ts +8 -7
  58. package/dist/form/validators/index.d.ts +1 -1
  59. package/dist/form/validators/integer.d.ts +8 -7
  60. package/dist/form/validators/is-date.d.ts +11 -9
  61. package/dist/form/validators/is-number.d.ts +11 -9
  62. package/dist/form/validators/max-age.d.ts +13 -9
  63. package/dist/form/validators/max-date.d.ts +8 -7
  64. package/dist/form/validators/max-file-size.d.ts +8 -7
  65. package/dist/form/validators/max-files.d.ts +8 -7
  66. package/dist/form/validators/max-length.d.ts +14 -7
  67. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  68. package/dist/form/validators/max.d.ts +10 -8
  69. package/dist/form/validators/min-age.d.ts +12 -9
  70. package/dist/form/validators/min-date.d.ts +8 -7
  71. package/dist/form/validators/min-file-size.d.ts +8 -7
  72. package/dist/form/validators/min-files.d.ts +11 -9
  73. package/dist/form/validators/min-length.d.ts +14 -7
  74. package/dist/form/validators/min.d.ts +10 -8
  75. package/dist/form/validators/multiple-of.d.ts +10 -8
  76. package/dist/form/validators/non-negative.d.ts +8 -7
  77. package/dist/form/validators/non-zero.d.ts +8 -7
  78. package/dist/form/validators/past-date.d.ts +8 -7
  79. package/dist/form/validators/pattern.d.ts +9 -8
  80. package/dist/form/validators/phone.d.ts +8 -7
  81. package/dist/form/validators/required.d.ts +11 -9
  82. package/dist/form/validators/url.d.ts +20 -13
  83. package/dist/form/validators/validation-issue.d.ts +8 -0
  84. package/dist/i18n/descriptor.d.ts +1 -1
  85. package/dist/i18n/en.json.d.ts +5 -1
  86. package/dist/i18n/locale-store.d.ts +7 -0
  87. package/dist/i18n/message-format.d.ts +24 -4
  88. package/dist/i18n/ru.json.d.ts +5 -1
  89. package/dist/i18n/translator.d.ts +11 -0
  90. package/dist/i18n/validation-message.d.ts +42 -7
  91. package/dist/i18n.d.ts +7 -12
  92. package/dist/i18n.js +66 -283
  93. package/dist/index.d.ts +16 -7
  94. package/dist/index.js +958 -2798
  95. package/dist/loader-BWkigpVe.js +52 -0
  96. package/dist/locale/en.js +1 -1
  97. package/dist/locale/en.json +4 -1
  98. package/dist/locale/ru.js +4 -1
  99. package/dist/locale/ru.json +4 -1
  100. package/dist/locale-CG54AU2Q.js +219 -0
  101. package/dist/locale.js +1 -1
  102. package/dist/model/behaviors-value.d.ts +24 -6
  103. package/dist/model/create-model.d.ts +2 -2
  104. package/dist/model/derived-registry.d.ts +1 -1
  105. package/dist/model/index.d.ts +16 -5
  106. package/dist/model/model-nodes.d.ts +138 -8
  107. package/dist/model/model-signals-proxy.d.ts +11 -5
  108. package/dist/model/model-value-proxy.d.ts +84 -3
  109. package/dist/model/safe-effect.d.ts +33 -1
  110. package/dist/model/types.d.ts +70 -17
  111. package/dist/model.js +21 -17
  112. package/dist/node-kind-C-r6ZMdo.js +15 -0
  113. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  114. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  115. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  116. package/dist/platforms/react/hooks/types.d.ts +57 -38
  117. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  118. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  119. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  120. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  121. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  122. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  123. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  124. package/dist/platforms/react/i18n/context.d.ts +6 -1
  125. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  126. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  127. package/dist/platforms/react/index.d.ts +7 -4
  128. package/dist/react.d.ts +22 -0
  129. package/dist/react.js +505 -0
  130. package/dist/runtime-token.d.ts +1 -2
  131. package/dist/safe-effect-BbtX3KuN.js +643 -0
  132. package/dist/schema-controller-CpiO2JoK.js +143 -0
  133. package/dist/signals.d.ts +15 -9
  134. package/dist/signals.js +11 -6
  135. package/dist/strategy-CoFSemuP.js +375 -0
  136. package/dist/type-guards-D-N_5Ddh.js +19 -0
  137. package/dist/validation-issue-B9mjI8jz.js +7 -0
  138. package/dist/validation-message-Ced9MGxs.js +194 -0
  139. package/dist/validation.js +9 -8
  140. package/dist/validators/email.js +5 -5
  141. package/dist/validators/future-date.js +8 -8
  142. package/dist/validators/integer.js +3 -3
  143. package/dist/validators/is-date.js +4 -4
  144. package/dist/validators/is-number.js +4 -4
  145. package/dist/validators/max-age.js +8 -9
  146. package/dist/validators/max-date.js +8 -8
  147. package/dist/validators/max-length.js +7 -7
  148. package/dist/validators/max.js +4 -4
  149. package/dist/validators/min-age.js +10 -8
  150. package/dist/validators/min-date.js +3 -3
  151. package/dist/validators/min-length.js +5 -5
  152. package/dist/validators/min.js +4 -4
  153. package/dist/validators/multiple-of.js +11 -7
  154. package/dist/validators/non-negative.js +3 -3
  155. package/dist/validators/non-zero.js +3 -3
  156. package/dist/validators/past-date.js +8 -8
  157. package/dist/validators/pattern.js +5 -5
  158. package/dist/validators/phone.js +11 -9
  159. package/dist/validators/required.js +2 -2
  160. package/dist/validators/url.js +48 -12
  161. package/dist/validators.js +17 -17
  162. package/docs/llms/01-api-reference.md +60 -34
  163. package/docs/llms/02-quick-start.md +111 -80
  164. package/docs/llms/03-api-signatures.md +368 -153
  165. package/docs/llms/04-common-patterns.md +58 -56
  166. package/docs/llms/05-common-mistakes.md +69 -44
  167. package/docs/llms/06-troubleshooting.md +14 -9
  168. package/docs/llms/07-complete-import.md +74 -43
  169. package/docs/llms/08-form-types.md +11 -4
  170. package/docs/llms/09-formschema.md +131 -83
  171. package/docs/llms/10-arrays.md +119 -69
  172. package/docs/llms/11-async-watchfield.md +2 -2
  173. package/docs/llms/13-multi-step.md +134 -63
  174. package/docs/llms/14-extended-mistakes.md +20 -17
  175. package/docs/llms/15-project-structure.md +221 -74
  176. package/docs/llms/16-ui-components.md +55 -42
  177. package/docs/llms/17-nonexistent-api.md +199 -32
  178. package/docs/llms/18-conditional-fields.md +66 -32
  179. package/docs/llms/19-reading-values.md +15 -3
  180. package/docs/llms/20-compute-vs-watch.md +16 -10
  181. package/docs/llms/21-array-operations.md +76 -26
  182. package/docs/llms/22-cycle-detection.md +1 -1
  183. package/docs/llms/23-copy-from.md +2 -2
  184. package/docs/llms/24-sync-fields.md +6 -5
  185. package/docs/llms/25-reset-when.md +14 -7
  186. package/docs/llms/26-transform-value.md +4 -4
  187. package/docs/llms/27-revalidate-when.md +16 -15
  188. package/docs/llms/28-submit-and-reset.md +276 -128
  189. package/docs/llms/29-async-preload.md +40 -30
  190. package/docs/llms/30-type-safety-recipes.md +105 -51
  191. package/docs/llms/31-async-validator-debounce.md +96 -53
  192. package/docs/llms/33-validation-strategy.md +178 -66
  193. package/docs/llms/34-i18n.md +69 -43
  194. package/docs/llms/35-unified-contract.md +307 -0
  195. package/docs/llms/36-issue-messages.md +284 -0
  196. package/docs/llms/37-compiled-schema.md +332 -0
  197. package/docs/llms/38-form-flow.md +397 -0
  198. package/llms-index.json +1 -1
  199. package/llms.txt +6968 -3278
  200. package/package.json +23 -4
  201. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  202. package/dist/create-model-B8U-COv-.js +0 -103
  203. package/dist/date-utils-dTT_x_Be.js +0 -31
  204. package/dist/form/create-core-form.d.ts +0 -70
  205. package/dist/form/factories/index.d.ts +0 -6
  206. package/dist/form/factories/node-factory.d.ts +0 -189
  207. package/dist/form/form-submitter.d.ts +0 -121
  208. package/dist/form/nodes/array-node.d.ts +0 -276
  209. package/dist/form/nodes/field-node.d.ts +0 -297
  210. package/dist/form/nodes/form-node.d.ts +0 -326
  211. package/dist/form/nodes/group-node.d.ts +0 -284
  212. package/dist/form/nodes/model-array-node.d.ts +0 -121
  213. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  214. package/dist/form/status-machine.d.ts +0 -115
  215. package/dist/form/types/deep-schema.d.ts +0 -107
  216. package/dist/form/types/form-proxy.d.ts +0 -141
  217. package/dist/form/unique-id.d.ts +0 -45
  218. package/dist/form/validation/config.d.ts +0 -73
  219. package/dist/form/validation/error-handler.d.ts +0 -166
  220. package/dist/form/validators/validation-error.d.ts +0 -8
  221. package/dist/index-BrNkQj1y.js +0 -105
  222. package/dist/loader-Dv_Hl7JC.js +0 -216
  223. package/dist/model-signals-proxy-2vqS243J.js +0 -250
  224. package/dist/node-DGEFMTQu.js +0 -18
  225. package/dist/strategy-BXS_gBfz.js +0 -170
  226. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,9 +1,12 @@
1
- import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
1
+ import { CompiledSchema } from './schema/compiled';
2
+ import { FormGroup } from './types/index';
2
3
  import { FormSchemaNode } from './types/schema-node';
3
4
  import { FormModel } from '../model/types';
4
5
  import { FormBehavior } from './behaviors';
6
+ import { SchemaController } from './schema-controller';
7
+ import { FormFlow } from './flow/flow';
5
8
  /**
6
- * Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
9
+ * Аргументы сборки формы из модели: данные приходят из {@link FormModel},
7
10
  * конфиг полей (component/componentProps) — из единой схемы.
8
11
  *
9
12
  * @group Utilities
@@ -12,33 +15,43 @@ export interface CreateFormFromModelArgs<T> {
12
15
  /** Реактивная модель данных (источник истины значений). */
13
16
  model: FormModel<T>;
14
17
  /**
15
- * Единая Schema (дерево узлов {@link FormSchemaNode}). createForm обходит её и привязывает конфиг
16
- * поля к ноде по идентичности сигнала (`node.value === model.$.path`). Опциональна.
18
+ * Схема — дерево узлов {@link FormSchemaNode}. Сборка обходит её и привязывает конфиг поля к
19
+ * ноде по идентичности ручки (`node.model === model.$.<path>`). Опциональна.
17
20
  */
18
21
  schema?: FormSchemaNode;
19
22
  /**
20
23
  * Декларативная схема поведения ({@link defineFormBehavior}). Запускается ПОСЛЕ построения нод и
21
- * заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
24
+ * заполнения реестра сигнал→нода; cleanup живёт на форме и снимается вместе с ней.
22
25
  */
23
26
  behavior?: FormBehavior<T>;
27
+ /**
28
+ * Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт
29
+ * сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.
30
+ * Без него поведение пишет правила в собственный контроллер, который никто не читает.
31
+ */
32
+ schemaController?: SchemaController;
33
+ /** Поток шагов сборки: его получает поведение (`defineFormBehavior(({ flow }) => …)`). */
34
+ flow?: FormFlow;
24
35
  }
25
36
  /**
26
- * Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).
37
+ * Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика).
27
38
  *
28
39
  * Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
29
- * ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
30
- * модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
31
- * top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
32
- * роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
40
+ * ссылаются на сигналы модели по идентичности. Обходит структуру модели и строит ноды по виду её
41
+ * узлов, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы
42
+ * под-форм ({@link FormArray}, на любой глубине), заполняет реестр ручка→нода (для
43
+ * `enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное
44
+ * поведение (cleanup живёт на форме).
33
45
  *
34
- * Обычно вызывается неявно через {@link createForm} с аргументом `{ model, schema }` — прямой вызов
35
- * нужен редко (например, для построения формы элемента массива).
46
+ * Обычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту
47
+ * фабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента
48
+ * массива, тесты, свой слой сборки.
36
49
  *
37
50
  * @typeParam T - Тип модели данных формы
38
51
  * @param args - Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}
39
- * @returns Типизированная форма с Proxy-доступом к полям {@link FormProxy}
52
+ * @returns Группа формы {@link FormGroup} с нодами полей по именам
40
53
  *
41
- * @example Форма из модели + схемы (эквивалент `createForm({ model, schema })`)
54
+ * @example Форма из модели и схемы
42
55
  * ```typescript
43
56
  * import { createModel, createFormFromModel } from '@reformer/core';
44
57
  *
@@ -51,10 +64,10 @@ export interface CreateFormFromModelArgs<T> {
51
64
  * const schema = {
52
65
  * component: Section,
53
66
  * children: [
54
- * // Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
55
- * { value: model.$.email, component: Input },
67
+ * // Layout несёт только component/componentProps; правила — в отдельной ValidationDefinition.
68
+ * { model: model.$.email, component: Input },
56
69
  * // вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
57
- * { value: model.$.profile.name, component: Input },
70
+ * { model: model.$.profile.name, component: Input },
58
71
  * ],
59
72
  * };
60
73
  *
@@ -65,69 +78,43 @@ export interface CreateFormFromModelArgs<T> {
65
78
  * console.log(model.email); // 'user@mail.com'
66
79
  * ```
67
80
  *
68
- * @see {@link createForm} - основная фабрика (диспетчеризует сюда при аргументе `{ model, schema }`)
81
+ * @see `createForm` — сборка одним вызовом: модель, форма, валидация и дерево для рендера
69
82
  * @group Utilities
70
83
  */
71
- export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
84
+ export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormGroup<T>;
72
85
  /**
73
- * Создать форму из {@link FormModel} + единой схемы (архитектура M1, рекомендуемый путь).
86
+ * Снять форму, собранную {@link createFormFromModel}: поведение формы и подписки нод (массивы
87
+ * под-форм следят за моделью). Значения модели остаются. Идемпотентно.
74
88
  *
75
- * @group Utilities
89
+ * Сборку одним вызовом снимают целиком — `bundle.dispose()`: вместе с формой она снимает живую
90
+ * проверку и отменяет идущие проверки. Этот вызов нужен своим слоям сборки поверх
91
+ * {@link createFormFromModel}.
76
92
  *
77
- * @param args - Модель данных, layout-схема (component/componentProps) и (опционально) поведение
78
- * @returns Типизированная форма с Proxy-доступом к полям
93
+ * @param form - Форма из {@link createFormFromModel}.
79
94
  *
80
- * @example Архитектура M1: `createModel` + layout-схема + `createForm({ model, schema })`
95
+ * @example
81
96
  * ```typescript
82
- * import { createModel, createForm } from '@reformer/core';
83
- * import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
84
- * import { required, email, minLength } from '@reformer/core/validators';
85
- *
86
- * interface UserForm {
87
- * email: string;
88
- * password: string;
89
- * }
90
- *
91
- * const model = createModel<UserForm>({ email: '', password: '' });
92
- *
93
- * // Layout-схема НЕ несёт validators — только component/componentProps.
94
- * const schema = {
95
- * children: [
96
- * { value: model.$.email, component: Input },
97
- * { value: model.$.password, component: Input },
98
- * ],
99
- * };
100
- *
101
- * const form = createForm<UserForm>({ model, schema });
102
- * form.email.setValue('test@mail.com');
103
- *
104
- * // Валидация — ОТДЕЛЬНАЯ схема, прогон внешним раннером (ошибки сами доезжают до нод):
105
- * const validation = defineValidationSchema<UserForm>(({ model }) => {
106
- * validate(model.$.email, [required(), email()]);
107
- * validate(model.$.password, [required(), minLength(8)]);
108
- * });
109
- * const ok = await validateModel(model, validation);
97
+ * const form = createFormFromModel<Profile>({ model, schema, behavior });
98
+ * // … форма больше не нужна
99
+ * disposeForm(form);
110
100
  * ```
101
+ *
102
+ * @group Utilities
111
103
  */
112
- export declare function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
113
- export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
104
+ export declare function disposeForm<T>(form: FormGroup<T>): void;
114
105
  /**
115
- * Создать форму только со схемой полей.
116
- *
117
- * @deprecated Legacy / back-compat: плоская `FormSchema` с инлайн-значениями (`value: ''`) — путь
118
- * ДО архитектуры M1. Для нового кода используйте перегрузку `createForm({ model, schema })`
119
- * (значения принадлежат {@link FormModel}, а правила — отдельной схеме `defineValidationSchema`,
120
- * которую прогоняет `validateModel`; поля `validators` у узла layout-схемы нет).
106
+ * Аргументы {@link createFormFromCompiled}: вместо авторского дерева — его компиляция.
121
107
  *
122
- * @param schema - Схема полей формы
123
- * @returns Типизированная форма с Proxy-доступом к полям
108
+ * @internal
109
+ */
110
+ export interface CreateFormFromCompiledArgs<T> extends Omit<CreateFormFromModelArgs<T>, 'schema'> {
111
+ /** Скомпилированная схема области модели; без неё у полей нет конфига из схемы. */
112
+ compiled?: CompiledSchema;
113
+ }
114
+ /**
115
+ * Форма из модели и скомпилированной схемы. Её зовут {@link createFormFromModel}, сборка одним
116
+ * вызовом (`createForm`) и форма строки массива — каждая со своей компиляцией.
124
117
  *
125
- * @example Legacy (back-compat) — плоская схема без модели
126
- * ```typescript
127
- * const form = createForm<UserForm>({
128
- * email: { value: '', component: Input },
129
- * password: { value: '', component: Input },
130
- * });
131
- * ```
118
+ * @internal
132
119
  */
133
- export declare function createForm<T>(schema: FormSchema<T>): FormProxy<T>;
120
+ export declare function createFormFromCompiled<T>(args: CreateFormFromCompiledArgs<T>): FormGroup<T>;
@@ -0,0 +1,87 @@
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { ValidationResult } from '../validation/types';
3
+ /**
4
+ * Почему поток остался на шаге: `invalid` — у шага блокирующие замечания, `error` — правило не
5
+ * вернуло результат, `cancelled` — прогон отменён либо результат проверки устарел (поток перевели,
6
+ * модель формы изменилась, пока шла проверка шага), `edge` — дальше (или раньше) шагов нет,
7
+ * `locked` — шаг недоступен, пока не пройден предыдущий, `current` — `goTo` на шаг, на котором поток
8
+ * уже стоит.
9
+ *
10
+ * @group Form
11
+ */
12
+ export type FlowStayReason = 'invalid' | 'error' | 'cancelled' | 'edge' | 'locked' | 'current';
13
+ /**
14
+ * Итог перехода: поток перешёл на другой шаг либо остался — с причиной и, если шаг проверялся,
15
+ * результатом проверки.
16
+ *
17
+ * @group Form
18
+ */
19
+ export type FlowTransition = {
20
+ readonly status: 'moved';
21
+ readonly from: string;
22
+ readonly to: string;
23
+ } | {
24
+ readonly status: 'stayed';
25
+ readonly step: string;
26
+ readonly reason: FlowStayReason;
27
+ readonly validation?: ValidationResult;
28
+ };
29
+ /**
30
+ * Поток шагов формы.
31
+ *
32
+ * @group Form
33
+ */
34
+ export interface FormFlow {
35
+ /** Идентификаторы шагов по порядку. */
36
+ readonly steps: readonly string[];
37
+ /** Текущий шаг. */
38
+ readonly current: ReadonlySignal<string>;
39
+ /** Номер текущего шага с нуля. */
40
+ readonly index: ReadonlySignal<number>;
41
+ /** Пройденные шаги: правила шага прошли при переходе дальше. */
42
+ readonly completed: ReadonlySignal<ReadonlySet<string>>;
43
+ readonly isFirst: ReadonlySignal<boolean>;
44
+ readonly isLast: ReadonlySignal<boolean>;
45
+ /** Идёт ли проверка шага в `next()`. */
46
+ readonly validating: ReadonlySignal<boolean>;
47
+ /** Можно ли перейти на шаг: первый — всегда, остальные — после прохождения предыдущего. */
48
+ canGoTo(stepId: string): boolean;
49
+ /** Проверить правила текущего шага и при успехе перейти к следующему. */
50
+ next(): Promise<FlowTransition>;
51
+ /** Вернуться на шаг назад; пройденными остаются только шаги до него. */
52
+ previous(): FlowTransition;
53
+ /** Перейти на шаг; неизвестный шаг — исключение. */
54
+ goTo(stepId: string): FlowTransition;
55
+ /** Вернуться на первый шаг и забыть пройденные. */
56
+ reset(): void;
57
+ }
58
+ /**
59
+ * Опции {@link createFormFlow}.
60
+ *
61
+ * @group Form
62
+ */
63
+ export interface FormFlowOptions {
64
+ /** Идентификаторы шагов по порядку; не пустой список без повторов. */
65
+ readonly steps: readonly string[];
66
+ /** Проверка шага при `next()`; без неё шаг проходится без проверки. */
67
+ readonly validateStep?: (stepId: string) => Promise<ValidationResult>;
68
+ }
69
+ /**
70
+ * Создать поток шагов.
71
+ *
72
+ * @param options - {@link FormFlowOptions}: шаги и проверка шага.
73
+ * @returns {@link FormFlow} на первом шаге.
74
+ * @throws Error если шагов нет или идентификатор повторяется.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const flow = createFormFlow({ steps: ['loan', 'contacts', 'confirm'] });
79
+ *
80
+ * await flow.next(); // { status: 'moved', from: 'loan', to: 'contacts' }
81
+ * flow.canGoTo('confirm'); // false — шаг contacts ещё не пройден
82
+ * flow.goTo('loan'); // { status: 'moved', from: 'contacts', to: 'loan' }
83
+ * ```
84
+ *
85
+ * @group Form
86
+ */
87
+ export declare function createFormFlow(options: FormFlowOptions): FormFlow;
@@ -0,0 +1,153 @@
1
+ import { FormModel } from '../model/types';
2
+ import { FormBehavior } from './behaviors';
3
+ import { SchemaController, SchemaNodeControl } from './schema-controller';
4
+ import { CompiledSchema } from './schema/compiled';
5
+ import { FormSchemaNode } from './types/schema-node';
6
+ import { FormGroup } from './types/index';
7
+ import { FormFlowConfig, FormLevel, FormValidation } from './form-level';
8
+ import { ValidationDefinition } from './validation';
9
+ /**
10
+ * Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до
11
+ * рендерера вместе с ним.
12
+ *
13
+ * @group Utilities
14
+ */
15
+ export interface ResolvedSchema {
16
+ /** Корневой узел дерева. */
17
+ tree: FormSchemaNode;
18
+ /** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */
19
+ fieldWrapper?: unknown;
20
+ /** Граница ошибок — компонент, которым рендерер оборачивает дерево. */
21
+ errorBoundary?: unknown;
22
+ }
23
+ /**
24
+ * Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра
25
+ * есть сторонние реализации, и ядро документ само не разбирает.
26
+ *
27
+ * @group Utilities
28
+ */
29
+ export interface SchemaResolver {
30
+ resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;
31
+ }
32
+ /**
33
+ * Часть бандла для рендерера: скомпилированная схема и схема-контроллер сборки.
34
+ *
35
+ * @group Utilities
36
+ */
37
+ export interface FormRender {
38
+ /** Скомпилированная схема корневой области; `undefined`, если схема не задана. */
39
+ readonly tree: CompiledSchema | undefined;
40
+ /** Схема-контроллер сборки: области корня, строк массивов и подформ. */
41
+ readonly controller: SchemaController;
42
+ /** Управление узлом КОРНЕВОЙ области по его `selector`. */
43
+ node(selector: string): SchemaNodeControl;
44
+ /** Обёртка поля из реестра (JSON-вариант). */
45
+ readonly fieldWrapper?: unknown;
46
+ /** Граница ошибок из реестра (JSON-вариант). */
47
+ readonly errorBoundary?: unknown;
48
+ }
49
+ /**
50
+ * Результат {@link createForm}: модель, форма, часть для рендерера и уровень формы
51
+ * ({@link FormLevel}) — `validate()`, `validateStep(id)`, поток шагов и отправка
52
+ * `submission.submit(handler)`.
53
+ *
54
+ * @typeParam T - Форма данных модели.
55
+ * @group Utilities
56
+ */
57
+ export interface FormBundle<T> extends FormLevel<T> {
58
+ readonly model: FormModel<T>;
59
+ readonly form: FormGroup<T>;
60
+ readonly render: FormRender;
61
+ /**
62
+ * Снять сборку: поведение формы, подписки нод, живую проверку; идущие проверки — полная, шага,
63
+ * отправки — получают `cancelled`. Значения модели остаются. Идемпотентно.
64
+ *
65
+ * Владелец сборки — тот, кто вызвал `createForm`: вне React (тесты, сервер, своя оболочка)
66
+ * снимайте её этим вызовом, когда форма больше не нужна.
67
+ */
68
+ dispose(): void;
69
+ }
70
+ /**
71
+ * Конфиг {@link createForm}.
72
+ *
73
+ * @typeParam T - Форма данных модели.
74
+ * @group Utilities
75
+ */
76
+ export interface CreateFormConfig<T> {
77
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
78
+ initial?: T;
79
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
80
+ model?: FormModel<T>;
81
+ /**
82
+ * Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.
83
+ * Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить
84
+ * дерево до модели нечем.
85
+ */
86
+ schema?: ((model: FormModel<T>) => FormSchemaNode) | object;
87
+ /** Реестр JSON-варианта: превращает документ схемы в дерево. */
88
+ registry?: SchemaResolver;
89
+ /** Поведение формы: значения, состояние нод и правила узлов схемы. */
90
+ behavior?: FormBehavior<T>;
91
+ /**
92
+ * Правила валидации: готовый набор либо {@link FormValidation} — полный набор `rules`, правила
93
+ * вне шагов `extras` и стратегия живой проверки. Без `rules` полный набор собирается из правил
94
+ * шагов потока и `extras`.
95
+ */
96
+ validation?: FormValidation<T> | ValidationDefinition<T>;
97
+ /**
98
+ * Поток шагов: `{ steps: [{ id, rules }] }`. Идентификатор шага — `selector` узла шага в схеме;
99
+ * `next()` потока проверяет правила текущего шага.
100
+ */
101
+ flow?: FormFlowConfig<T>;
102
+ /**
103
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
104
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
105
+ * инициализации не срабатывают). Модель, созданная из `initial`, снимает точку отсчёта после
106
+ * `seed` — сброс его правки не откатывает; точкой отсчёта переданной `model` управляет владелец.
107
+ */
108
+ seed?: (model: FormModel<T>) => void;
109
+ /** Донастройка ПОСЛЕ сборки: получает готовый бандл. */
110
+ setup?: (bundle: FormBundle<T>) => void;
111
+ }
112
+ /**
113
+ * Собрать модель, форму, уровень формы (проверка, отправка, поток шагов) и дерево для рендера за
114
+ * один проход.
115
+ *
116
+ * @typeParam T - Форма данных модели.
117
+ * @param config - {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,
118
+ * `behavior`, `validation`, `flow`, `seed`, `setup`.
119
+ * @returns {@link FormBundle} — `{ model, form, render, flow, validate, validateStep, submission, … }`.
120
+ *
121
+ * @example Рендерер по TS-схеме
122
+ * ```tsx
123
+ * const credit = useFormBundle(() =>
124
+ * createForm<CreditForm>({
125
+ * model: createCreditModel(),
126
+ * schema: creditSchema, // (model) => узел
127
+ * behavior: creditBehavior,
128
+ * validation: { extras: crossRules },
129
+ * flow: { steps: [{ id: 'loan', rules: loanRules }, { id: 'confirm' }] },
130
+ * })
131
+ * );
132
+ * return <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;
133
+ * ```
134
+ *
135
+ * @example React-руками — та же сборка, разметка в JSX
136
+ * ```tsx
137
+ * const credit = useFormBundle(() => createForm<CreditForm>({ initial, schema: creditSchema }));
138
+ * const send = async () => {
139
+ * const result = await credit.submission.submit(api.send);
140
+ * if (result.status === 'invalid') showHint('Проверьте форму');
141
+ * };
142
+ * return <FormField control={credit.form.loanType} />;
143
+ * ```
144
+ *
145
+ * @example JSON — документ схемы и реестр
146
+ * ```tsx
147
+ * const credit = useFormBundle(() =>
148
+ * createForm<CreditForm>({ model: createCreditModel(), schema: creditJson, registry })
149
+ * );
150
+ * return <FormRenderer form={credit} />;
151
+ * ```
152
+ */
153
+ export declare function createForm<T extends object>(config: CreateFormConfig<T>): FormBundle<T>;
@@ -0,0 +1,46 @@
1
+ import { FormModel } from '../model/types';
2
+ import { FormBehavior } from './behaviors';
3
+ import { FormGroup } from './types/index';
4
+ import { FormFlowConfig, FormLevel, FormValidation } from './form-level';
5
+ import { ValidationDefinition } from './validation';
6
+ /**
7
+ * Общая часть конфига всех фабрик формы.
8
+ *
9
+ * @typeParam T - Форма данных модели.
10
+ * @typeParam B - Бандл конкретной фабрики (его получает {@link CreateFormConfigBase.setup}).
11
+ */
12
+ export interface CreateFormConfigBase<T, B> {
13
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
14
+ initial?: T;
15
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
16
+ model?: FormModel<T>;
17
+ /** Декларативное поведение модели (compute/copyFrom/enableWhen/onChange). */
18
+ behavior?: FormBehavior<T>;
19
+ /** Правила валидации: готовый набор либо {@link FormValidation} с `extras` и стратегией. */
20
+ validation?: FormValidation<T> | ValidationDefinition<T>;
21
+ /** Поток шагов: идентификатор шага и его правила — {@link FormFlowConfig}. */
22
+ flow?: FormFlowConfig<T>;
23
+ /**
24
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
25
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
26
+ * инициализации не срабатывают). Модель, созданная из `initial`, снимает точку отсчёта после
27
+ * `seed` — сброс его правки не откатывает; точкой отсчёта переданной `model` управляет владелец.
28
+ */
29
+ seed?: (model: FormModel<T>) => void;
30
+ /** Донастройка ПОСЛЕ сборки: получает готовый бандл фабрики. */
31
+ setup?: (bundle: B) => void;
32
+ }
33
+ /**
34
+ * Общая часть результата фабрик: модель, форма и уровень формы ({@link FormLevel}) — проверка,
35
+ * отправка и поток шагов.
36
+ */
37
+ export interface CoreForm<T> extends FormLevel<T> {
38
+ readonly model: FormModel<T>;
39
+ readonly form: FormGroup<T>;
40
+ /**
41
+ * Снять сборку: форму (`disposeForm` — поведение, подписки нод) и уровень формы (живая
42
+ * проверка, идущие проверки). Фабрика обязана задать его ПОСЛЕ `...level`: `dispose` уровня
43
+ * форму не снимает.
44
+ */
45
+ dispose(): void;
46
+ }
@@ -0,0 +1,121 @@
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { FormModel } from '../model/types';
3
+ import { FormFlow } from './flow/flow';
4
+ import { FormSubmission } from './submission';
5
+ import { FormValidationController, ValidationStrategyKind } from './validation/strategy';
6
+ import { ValidationDefinition, ValidationResult } from './validation/types';
7
+ /**
8
+ * Правила валидации формы и стратегия живой проверки.
9
+ *
10
+ * ⚠️ `rules` и `extras` обязаны быть **стабильными ссылками** (`defineValidation` в модуле): отмена
11
+ * устаревших прогонов ключится по паре «модель + правила».
12
+ *
13
+ * @group Form
14
+ */
15
+ export interface FormValidation<T> {
16
+ /** Полный набор правил. Без него собирается из правил шагов потока и `extras`. */
17
+ rules?: ValidationDefinition<T>;
18
+ /** Правила, которые проверяются только целиком (отправка), но не по шагам. */
19
+ extras?: ValidationDefinition<T>;
20
+ /** Когда запускать живую проверку. По умолчанию `'submit'` — живой проверки нет. */
21
+ strategy?: ValidationStrategyKind;
22
+ debounce?: number;
23
+ /** Режим живой фазы для `afterFirstSubmit`. По умолчанию `'change'`. */
24
+ liveAfterSubmit?: 'change' | 'blur';
25
+ }
26
+ /**
27
+ * Шаг потока: идентификатор (он же `selector` узла шага в схеме) и правила шага.
28
+ *
29
+ * @group Form
30
+ */
31
+ export interface FlowStep<T> {
32
+ readonly id: string;
33
+ /** Правила шага; `null` или без поля — шаг без правил. */
34
+ readonly rules?: ValidationDefinition<T> | null;
35
+ }
36
+ /**
37
+ * Поток шагов в конфиге сборки.
38
+ *
39
+ * @group Form
40
+ */
41
+ export interface FormFlowConfig<T> {
42
+ readonly steps: readonly FlowStep<T>[];
43
+ }
44
+ /**
45
+ * Правила формы и контроллер живой проверки — его армирует `useFormBundle`.
46
+ *
47
+ * @group Form
48
+ */
49
+ export interface FormValidationState<T> {
50
+ /** Полный набор правил (стабильная ссылка). */
51
+ readonly rules: ValidationDefinition<T>;
52
+ readonly controller: FormValidationController;
53
+ }
54
+ /**
55
+ * Уровень формы в бандле сборки.
56
+ *
57
+ * @group Form
58
+ */
59
+ export interface FormLevel<T> {
60
+ /** Поток шагов; без `flow` в конфиге — `undefined`. */
61
+ readonly flow: FormFlow | undefined;
62
+ /** Идёт ли проверка: полная или шага. */
63
+ readonly validating: ReadonlySignal<boolean>;
64
+ /** Правила и контроллер живой проверки; без правил — `undefined`. */
65
+ readonly validation: FormValidationState<T> | undefined;
66
+ /**
67
+ * Отправка: `submission.submit(handler)` — один прогон всех правил и обработчик со значениями,
68
+ * которые они видели; `submission.submitting` — идёт ли обработчик.
69
+ */
70
+ readonly submission: FormSubmission<T>;
71
+ /**
72
+ * Проверить всю форму и показать замечания. Поля выключенных нод (`enableWhen`, `disable()`)
73
+ * не учитываются — статус совпадает с `valid` формы.
74
+ *
75
+ * Правила читают модель в момент вызова. Исключение — вызов в одном тике со сборкой формы или
76
+ * правкой поля: поведение ещё пишет отложенно (колбэк `onChange`, сброс `resetOnDisable`), и
77
+ * проверка сначала дожидается этих записей; `validating` поднят сразу. Так же ведут себя
78
+ * `validateStep` и отправка.
79
+ *
80
+ * Попыткой отправки проверка не считается: стратегия `afterFirstSubmit` остаётся в тихой фазе.
81
+ */
82
+ validate(): Promise<ValidationResult>;
83
+ /**
84
+ * Проверить шаг потока (без выключенных полей); неизвестный шаг — исключение.
85
+ *
86
+ * Это отчёт о шаге, как `validate` — о форме. Переход делает `flow.next()`: он сверяет модель
87
+ * и не переходит, если её изменили, пока шла проверка шага.
88
+ */
89
+ validateStep(stepId: string): Promise<ValidationResult>;
90
+ /**
91
+ * Снять уровень формы: подписки и таймеры живой проверки; идущие проверки — полная, шага,
92
+ * отправки — получают `cancelled`, и их замечания на ноды не ложатся. Идемпотентно. Ноды формы
93
+ * уровень не снимает — это делает сборка (`bundle.dispose()`).
94
+ */
95
+ dispose(): void;
96
+ }
97
+ /**
98
+ * Собрать уровень формы: правила, контроллер живой проверки, поток шагов и отправку.
99
+ *
100
+ * @typeParam T - Форма данных модели.
101
+ * @param model - Модель формы.
102
+ * @param config - Правила (`validation`) и поток (`flow`).
103
+ * @returns {@link FormLevel}.
104
+ * @throws Error если в потоке повторяется шаг.
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const level = createFormLevel(model, {
109
+ * validation: { extras: crossRules },
110
+ * flow: { steps: [{ id: 'loan', rules: loanRules }, { id: 'confirm' }] },
111
+ * });
112
+ * const result = await level.submission.submit(sendApplication);
113
+ * if (result.status === 'invalid') showErrors();
114
+ * ```
115
+ *
116
+ * @group Form
117
+ */
118
+ export declare function createFormLevel<T>(model: FormModel<T>, config: {
119
+ validation?: FormValidation<T> | ValidationDefinition<T>;
120
+ flow?: FormFlowConfig<T>;
121
+ }): FormLevel<T>;
@@ -1,25 +1,9 @@
1
- import { FormNode } from './nodes/form-node';
2
- import { FormProxy } from './types/form-proxy';
1
+ import { FormGroup } from './types/nodes';
2
+ import { InternalFormNode } from './nodes/node-kind';
3
3
  /**
4
- * Создать Proxy для типобезопасного доступа к полям GroupNode
4
+ * Прокси группы над её сырым узлом.
5
5
  *
6
- * Proxy обеспечивает:
7
- * - Доступ к полям через точечную нотацию (form.email, form.address.city)
8
- * - Приоритет собственных свойств GroupNode над полями
9
- * - Цепочку proxy для вложенных GroupNode
10
- * - Блокировку прямого присваивания полям (только через setValue/patchValue)
11
- *
12
- * @param target - GroupNode для которого создаётся proxy
13
- * @param fields - Map полей формы
14
- * @returns Типизированный Proxy
15
- *
16
- * @internal
17
- *
18
- * @example
19
- * ```typescript
20
- * const proxy = buildFormProxy(groupNode, groupNode._fields);
21
- * proxy.email.setValue('test@example.com');
22
- * console.log(proxy.email.value.value);
23
- * ```
6
+ * @param target - Сырой узел группы
7
+ * @param children - Сырые ноды полей по именам
24
8
  */
25
- export declare function buildFormProxy<T>(target: FormNode<T>, fields: Map<keyof T, FormNode<unknown>>): FormProxy<T>;
9
+ export declare function buildFormProxy<T>(target: InternalFormNode<T>, children: ReadonlyMap<string, InternalFormNode>): FormGroup<T>;