@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
@@ -0,0 +1,153 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ import { CompiledSchema } from './schema/compiled';
3
+ /**
4
+ * Хуки жизненного цикла узла схемы. Каждый хук опционален; повторная регистрация перезаписывает
5
+ * предыдущее значение.
6
+ *
7
+ * @group Schema
8
+ */
9
+ export interface NodeLifecycleHooks {
10
+ /** Срабатывает один раз при mount узла. Может вернуть cleanup-функцию. */
11
+ onMount?: () => void | (() => void);
12
+ /** Срабатывает один раз при unmount узла. */
13
+ onUnmount?: () => void;
14
+ }
15
+ /**
16
+ * Хранилище переопределений одной области схемы. Ключ каждой карты — `selector` узла: он же
17
+ * идентификатор узла в скомпилированной схеме области ({@link SchemaNodeId}).
18
+ *
19
+ * @group Schema
20
+ */
21
+ export interface SchemaOverrideMaps {
22
+ hiddenOverrides: Map<string, boolean | null>;
23
+ propsOverrides: Map<string, Record<string, unknown> | null>;
24
+ refRegistry: Map<string, {
25
+ current: any;
26
+ }>;
27
+ /** Условия скрытия узлов: selector → реактивное условие. */
28
+ conditionRegistry: Map<string, () => boolean>;
29
+ /** Реактивные эффекты, живущие, пока область смонтирована (`renderEffect`). */
30
+ effectRegistry: Array<() => void | (() => void)>;
31
+ /** Обработчики событий компонентов: selector → { имя пропа → обработчик }. */
32
+ callbackRegistry: Map<string, Map<string, (...args: any[]) => any>>;
33
+ /** Хуки жизненного цикла узла: selector → { onMount, onUnmount }. */
34
+ lifecycleRegistry: Map<string, NodeLifecycleHooks>;
35
+ /** Общий счётчик изменений области. Узлы на него не подписываются — см. `versionFor`. */
36
+ version: Signal<number>;
37
+ /**
38
+ * Версия-сигнал одного селектора (создаётся лениво). Узел подписывается только на сигнал СВОЕГО
39
+ * селектора, поэтому `setHidden` / `patchProps` уведомляют один узел, а не всё дерево.
40
+ */
41
+ versionFor(selector: string): Signal<number>;
42
+ }
43
+ /**
44
+ * Управление одним узлом схемы. Получается через `schema.node(selector)`.
45
+ *
46
+ * @group Schema
47
+ */
48
+ export interface SchemaNodeControl {
49
+ /** Принудительно скрыть или показать узел, минуя условие `hideWhen`. */
50
+ setHidden(value: boolean): this;
51
+ /** Убрать принудительное значение — снова действует условие `hideWhen`. */
52
+ resetHidden(): this;
53
+ /** Подмешать объект в `componentProps` узла. */
54
+ patchProps(partial: Record<string, unknown>): this;
55
+ /** Убрать подмешанные пропсы. */
56
+ resetProps(): this;
57
+ /**
58
+ * Ref на компонент узла. Создаётся один раз на селектор; рендерер вешает его на компонент.
59
+ * Компонент обязан принимать `ref`.
60
+ */
61
+ getRef<H>(): {
62
+ current: H | null;
63
+ };
64
+ /**
65
+ * @internal Записать условие скрытия узла (`hideWhen`). Возвращает снятие: оно убирает условие,
66
+ * только если его не сменило более позднее.
67
+ */
68
+ addHideCondition(condition: () => boolean): () => void;
69
+ /**
70
+ * @internal Записать обработчик проп-события компонента узла (`onComponentEvent`). Возвращает
71
+ * снятие: оно убирает обработчик, только если его не сменил более поздний.
72
+ */
73
+ addEventHandler(event: string, handler: (...args: any[]) => any): () => void;
74
+ /**
75
+ * @internal Записать хук жизненного цикла узла (`onMount`, `onUnmount`). Возвращает снятие: оно
76
+ * убирает хук, только если его не сменил более поздний.
77
+ */
78
+ addLifecycleHook<K extends keyof NodeLifecycleHooks>(hook: K, value: NonNullable<NodeLifecycleHooks[K]>): () => void;
79
+ /** @internal Селектор узла. */
80
+ __selector: string;
81
+ /** @internal Хранилище области — его читает рендерер. */
82
+ __overrideMaps: SchemaOverrideMaps;
83
+ }
84
+ /**
85
+ * Схема одной области: адресует узлы по `selector`.
86
+ *
87
+ * @group Schema
88
+ */
89
+ export interface SchemaScope {
90
+ /** Управление узлом области по его `selector`. */
91
+ node(selector: string): SchemaNodeControl;
92
+ /**
93
+ * @internal Записать эффект, живущий, пока область смонтирована (`renderEffect`). Возвращает
94
+ * снятие.
95
+ */
96
+ addRenderEffect(effect: () => void | (() => void)): () => void;
97
+ /** @internal Хранилище области — его читает рендерер. */
98
+ __overrideMaps: SchemaOverrideMaps;
99
+ }
100
+ /**
101
+ * Схема-контроллер сборки: области по под-моделям.
102
+ *
103
+ * @group Schema
104
+ */
105
+ export interface SchemaController {
106
+ /**
107
+ * Область под-модели: корня сборки, строки массива или группы подформы. Создаётся при первом
108
+ * обращении; повторное обращение отдаёт ту же область.
109
+ */
110
+ scopeOf(subModel: object): SchemaScope;
111
+ }
112
+ /**
113
+ * Схема области поверх хранилища: `node(selector)` → управление узлом.
114
+ *
115
+ * @param maps - Хранилище области; по умолчанию создаётся новое.
116
+ * @group Schema
117
+ */
118
+ export declare function createSchemaScope(maps?: SchemaOverrideMaps): SchemaScope;
119
+ /**
120
+ * Схема-контроллер одной сборки формы.
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const controller = createSchemaController();
125
+ * const root = controller.scopeOf(model);
126
+ * root.node('mortgage').setHidden(true);
127
+ *
128
+ * controller.scopeOf(model.items.at(0)); // область строки — свои селекторы
129
+ * ```
130
+ *
131
+ * @group Schema
132
+ */
133
+ export declare function createSchemaController(): SchemaController;
134
+ /**
135
+ * Селекторы, на которые в области записаны правила, но которых нет в её скомпилированной схеме.
136
+ * Такое правило ничего не делает — сборка и рендерер в dev предупреждают о нём.
137
+ *
138
+ * Ref в проверку не входит: его разрешено брать по абсолютному пути модели
139
+ * (`phones.0.number`), а такой строки при сборке может ещё не быть.
140
+ *
141
+ * @param maps - Хранилище области.
142
+ * @param compiled - Скомпилированная схема области ({@link CompiledSchema}).
143
+ * @returns Селекторы с правилами, не найденные в схеме области.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const scope = controller.scopeOf(model);
148
+ * unknownSchemaSelectors(scope.__overrideMaps, bundle.render.tree!); // ['mortgage'] — узла нет
149
+ * ```
150
+ *
151
+ * @group Schema
152
+ */
153
+ export declare function unknownSchemaSelectors(maps: SchemaOverrideMaps, compiled: CompiledSchema): string[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Поддерево схемы для под-модели: содержимое строки массива (`item`) или подформы (`part`).
3
+ *
4
+ * Билдер поддерева зовут два места — сборка формы и рендерер. Оба обязаны получить ОДНО дерево:
5
+ * на объектах его узлов держится идентичность (ключи рендера, записи схемы-контроллера), а сам
6
+ * билдер не обязан быть чистым. Поэтому дерево строится один раз на пару «билдер + под-модель»
7
+ * и дальше отдаётся из памяти.
8
+ *
9
+ * Память привязана к под-модели: исчезла строка массива — исчезли и её поддеревья.
10
+ *
11
+ * @group Utilities
12
+ * @module form/schema-subtree
13
+ */
14
+ /**
15
+ * Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.
16
+ *
17
+ * @typeParam N - Тип узла, который возвращает билдер.
18
+ * @param builder - Билдер поддерева: `item` узла-массива или `part` узла-подформы.
19
+ * @param subModel - Под-модель строки массива или группы.
20
+ * @returns Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const row = model.items.at(0);
25
+ * schemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true
26
+ * ```
27
+ */
28
+ export declare function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N;
@@ -1,46 +1,51 @@
1
- import { Signal } from '@preact/signals-core';
2
- import { FormNode } from './nodes/form-node';
1
+ import { FormNode } from './types/nodes';
3
2
  /**
4
- * Связать сигнал модели с его нодой формы.
3
+ * Связать ручку модели с её нодой формы.
5
4
  *
6
- * Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
7
- * листового поля. Прикладной код обычно этот реестр не заполняет напрямую.
5
+ * Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
6
+ * группы и массива под-форм; у группы в реестре лежит её лицо {@link FormGroup} с нодами полей.
7
+ * Прикладной код обычно этот реестр не заполняет напрямую.
8
8
  *
9
- * @param signal - Сигнал значения из {@link FormModel} (ручка поля, `model.$.path`)
10
- * @param node - Нода формы, отвечающая за это поле
9
+ * @param signal - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
10
+ * @param node - Нода формы, отвечающая за эту часть модели
11
11
  *
12
- * @example Привязка листовых полей при построении формы
12
+ * @example Ручка модели указывает на ноду другой сборки
13
13
  * ```typescript
14
14
  * import { registerSignalNode } from '@reformer/core';
15
15
  *
16
- * const sig = model.signalAt('profile.email');
17
- * const node = group.getFieldByPath('profile.email');
18
- * if (sig && node) registerSignalNode(sig, node);
16
+ * registerSignalNode(model.$.profile.email, profileForm.email);
19
17
  * ```
20
18
  *
21
- * @see {@link getNodeForSignal} - обратный поиск ноды по сигналу
19
+ * @see {@link getNodeForSignal} - обратный поиск ноды по ручке
22
20
  * @group Utilities
23
21
  */
24
- export declare function registerSignalNode(signal: Signal<any>, node: FormNode<any>): void;
22
+ export declare function registerSignalNode(signal: object, node: FormNode<any>): void;
25
23
  /**
26
- * Найти ноду формы по сигналу модели.
24
+ * Найти ноду формы по ручке модели.
27
25
  *
28
- * Используется state-операциями behavior (`enableWhen`/`disableWhen`) и роутингом ошибок
29
- * валидации в ноды. Возвращает `undefined`, если форма ещё не построена или поле не
30
- * материализовано в этой форме (например, элемент массива, который строится per-item).
26
+ * Используется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем
27
+ * (`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма
28
+ * ещё не построена или эта часть модели в форме не материализована (например, массив без
29
+ * item-схемы).
31
30
  *
32
- * @param signal - Сигнал значения из {@link FormModel}
33
- * @returns Нода формы для этого поля или `undefined`
31
+ * @param signal - Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`
32
+ * @returns Нода формы — поля, группы или массива — либо `undefined`
34
33
  *
35
34
  * @example Роутинг ошибок валидации в ноду поля
36
35
  * ```typescript
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
- * @see {@link registerSignalNode} - регистрация связи сигнал→нода
42
+ * @example Нода группы и строки массива
43
+ * ```typescript
44
+ * getNodeForSignal(model.$.address)?.disable(); // группа целиком
45
+ * getNodeForSignal(model.$.phones[0])?.markAsTouched(); // форма строки массива
46
+ * ```
47
+ *
48
+ * @see {@link registerSignalNode} - регистрация связи ручка→нода
44
49
  * @group Utilities
45
50
  */
46
- export declare function getNodeForSignal(signal: Signal<any>): FormNode<any> | undefined;
51
+ export declare function getNodeForSignal(signal: object): FormNode<any> | undefined;
@@ -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
+ }