@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
@@ -0,0 +1,332 @@
1
+ # Скомпилированная схема — compileSchema, виды узлов и области
2
+
3
+ ## Purpose
4
+
5
+ Авторская схема (`FormSchemaNode`) описывает вид узла ключами: `model`, `item`, `part`,
6
+ `children`. Разбирать их по-своему раньше приходилось каждому, кто обходил дерево. Теперь дерево
7
+ разбирает один компилятор — `compileSchema(tree, model)` — и все читают его итог,
8
+ `CompiledSchema`: у каждого узла явный вид (`kind`), нормализованная привязка к модели,
9
+ идентификатор и область.
10
+
11
+ `createForm` компилирует дерево **один раз**: ноды формы и рендерер читают одну и ту же
12
+ компиляцию, она лежит в `bundle.render.tree`. Сам `compileSchema` нужен редко — своему
13
+ рендереру, инструментам (инспектор дерева, проверка селекторов в тестах) и фабрикам, которые
14
+ собирают форму без `createForm`.
15
+
16
+ ## API
17
+
18
+ ```typescript
19
+ import {
20
+ compileSchema,
21
+ isCompiledNode,
22
+ type CompiledSchema,
23
+ type CompiledNode,
24
+ type CompiledChild,
25
+ type SchemaNodeId,
26
+ type SchemaScopeId,
27
+ } from '@reformer/core';
28
+
29
+ function compileSchema(
30
+ tree: FormSchemaNode,
31
+ model?: object, // модель области: корень сборки, под-модель строки или группы
32
+ options?: { scope?: SchemaScopeId } // по умолчанию корень '#'
33
+ ): CompiledSchema;
34
+
35
+ function isCompiledNode(value: unknown): value is CompiledNode; // узел компилятора, а не автора
36
+
37
+ interface CompiledSchema {
38
+ readonly scope: SchemaScopeId; // '#', '#/address', '#/phones[3]'
39
+ readonly model: object | undefined; // модель области
40
+ readonly root: CompiledNode; // корневой узел области
41
+ readonly nodes: ReadonlyMap<SchemaNodeId, CompiledNode>; // узлы ЭТОЙ области по id
42
+ readonly fields: ReadonlyMap<object, CompiledFieldNode>; // поля по ручке — области и её подформ
43
+ readonly arrays: ReadonlyMap<object, CompiledArrayNode>; // массивы под-форм по ручке
44
+ }
45
+ ```
46
+
47
+ ### Виды узлов — `kind`
48
+
49
+ | `kind` | Узел автора | Что добавляет компилятор |
50
+ | ----------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
51
+ | `field` | `{ model: model.$.<лист или массив целиком>, component }` | `handle` (ручка значения), `disabled` |
52
+ | `array` | `{ model: model.$.<массив>, item: (row) => узел }` | `handle`, `control` (фасад массива модели), `initialValue`, `row(rowModel)` |
53
+ | `part` | `{ model: model.$.<группа>, part: (sub) => узел }` | `handle`, `model` (под-модель группы), `content` — схема части |
54
+ | `container` | всё остальное: `children`, `component` | `children: CompiledChild[]` — узлы и текст по порядку |
55
+ | `text` | подряд идущие строки, числа, сигналы среди `children` | `parts` — склеенные части; только ребёнок контейнера |
56
+
57
+ Общие поля узла (кроме `text`): `id`, `path` (позиционный путь `#/0/2`), `selector`, `scope`,
58
+ `component` (как есть — ядро его не интерпретирует), `props` (`componentProps` автора, без них —
59
+ пустой объект), `source` (авторский узел).
60
+
61
+ Порядок распознавания важен: ручка массива — тоже ручка значения, поэтому сначала проверяется
62
+ `item` (массив под-форм), затем `part` (подформа), затем привязка-значение (поле), остальное —
63
+ контейнер. Контейнер без `component` рендерер рисует как одних детей.
64
+
65
+ ### Идентификатор узла — `id`
66
+
67
+ - Узел с `selector` — `id` равен `selector`.
68
+ - Узел без `selector` — позиционный путь от корня области: `#`, `#/0`, `#/0/2`.
69
+ - Текст — позиционный путь первой части.
70
+ - `SchemaNodeId` — строка с брендом: в `compiled.nodes.get(...)` ключ приводят
71
+ (`'email' as SchemaNodeId`).
72
+
73
+ ### Области — `scope`
74
+
75
+ | Область | Идентификатор | Что в ней |
76
+ | ---------------- | ---------------------------- | --------------------------------------- |
77
+ | корень сборки | `#` | дерево `schema(model)` |
78
+ | подформа (`part`) | `#/registrationAddress` | содержимое части; путь группы от модели области |
79
+ | строка массива | `#/phones[<ключ>]` | разметка строки из `item(rowModel)` |
80
+ | вложенные | `#/coBorrowers[3]/phones[7]` | область внутри области |
81
+
82
+ - В скобках строки — **стабильный ключ** строки, а не индекс: он держится за элемент модели и не
83
+ меняется при `move` / `swap`. Тот же ключ отдаёт `FormArray.keyAt(index)`.
84
+ - **`selector` уникален в своей области.** Повтор в одной области — исключение при сборке с путями
85
+ обоих узлов. Селекторы строк массива и подформ живут в своих областях и с корнем не
86
+ пересекаются: одна часть, подключённая дважды, даёт две области без конфликта.
87
+ - По `selector` узел адресуют поведение (`schema.node(selector)` — в своей области) и шаги потока
88
+ (`flow.steps[].id` — `selector` узла шага в корневой области).
89
+
90
+ ### Подформы и строки
91
+
92
+ - **Подформа** компилируется сразу; её содержимое — `content` (своя `CompiledSchema`). Поля и
93
+ массивы части попадают и в `fields` / `arrays` родителя: у части нет отдельной формы, её поля
94
+ принадлежат той же сборке (`form.registrationAddress.city`). Подформа на самой модели области
95
+ (`model: model.# Скомпилированная схема — compileSchema, виды узлов и области
96
+
97
+ ## Purpose
98
+
99
+ Авторская схема (`FormSchemaNode`) описывает вид узла ключами: `model`, `item`, `part`,
100
+ `children`. Разбирать их по-своему раньше приходилось каждому, кто обходил дерево. Теперь дерево
101
+ разбирает один компилятор — `compileSchema(tree, model)` — и все читают его итог,
102
+ `CompiledSchema`: у каждого узла явный вид (`kind`), нормализованная привязка к модели,
103
+ идентификатор и область.
104
+
105
+ `createForm` компилирует дерево **один раз**: ноды формы и рендерер читают одну и ту же
106
+ компиляцию, она лежит в `bundle.render.tree`. Сам `compileSchema` нужен редко — своему
107
+ рендереру, инструментам (инспектор дерева, проверка селекторов в тестах) и фабрикам, которые
108
+ собирают форму без `createForm`.
109
+
110
+ ## API
111
+
112
+ ```typescript
113
+ import {
114
+ compileSchema,
115
+ isCompiledNode,
116
+ type CompiledSchema,
117
+ type CompiledNode,
118
+ type CompiledChild,
119
+ type SchemaNodeId,
120
+ type SchemaScopeId,
121
+ } from '@reformer/core';
122
+
123
+ function compileSchema(
124
+ tree: FormSchemaNode,
125
+ model?: object, // модель области: корень сборки, под-модель строки или группы
126
+ options?: { scope?: SchemaScopeId } // по умолчанию корень '#'
127
+ ): CompiledSchema;
128
+
129
+ function isCompiledNode(value: unknown): value is CompiledNode; // узел компилятора, а не автора
130
+
131
+ interface CompiledSchema {
132
+ readonly scope: SchemaScopeId; // '#', '#/address', '#/phones[3]'
133
+ readonly model: object | undefined; // модель области
134
+ readonly root: CompiledNode; // корневой узел области
135
+ readonly nodes: ReadonlyMap<SchemaNodeId, CompiledNode>; // узлы ЭТОЙ области по id
136
+ readonly fields: ReadonlyMap<object, CompiledFieldNode>; // поля по ручке — области и её подформ
137
+ readonly arrays: ReadonlyMap<object, CompiledArrayNode>; // массивы под-форм по ручке
138
+ }
139
+ ```
140
+
141
+ ### Виды узлов — `kind`
142
+
143
+ | `kind` | Узел автора | Что добавляет компилятор |
144
+ | ----------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
145
+ | `field` | `{ model: model.$.<лист или массив целиком>, component }` | `handle` (ручка значения), `disabled` |
146
+ | `array` | `{ model: model.$.<массив>, item: (row) => узел }` | `handle`, `control` (фасад массива модели), `initialValue`, `row(rowModel)` |
147
+ | `part` | `{ model: model.$.<группа>, part: (sub) => узел }` | `handle`, `model` (под-модель группы), `content` — схема части |
148
+ | `container` | всё остальное: `children`, `component` | `children: CompiledChild[]` — узлы и текст по порядку |
149
+ | `text` | подряд идущие строки, числа, сигналы среди `children` | `parts` — склеенные части; только ребёнок контейнера |
150
+
151
+ Общие поля узла (кроме `text`): `id`, `path` (позиционный путь `#/0/2`), `selector`, `scope`,
152
+ `component` (как есть — ядро его не интерпретирует), `props` (`componentProps` автора, без них —
153
+ пустой объект), `source` (авторский узел).
154
+
155
+ Порядок распознавания важен: ручка массива — тоже ручка значения, поэтому сначала проверяется
156
+ `item` (массив под-форм), затем `part` (подформа), затем привязка-значение (поле), остальное —
157
+ контейнер. Контейнер без `component` рендерер рисует как одних детей.
158
+
159
+ ### Идентификатор узла — `id`
160
+
161
+ - Узел с `selector` — `id` равен `selector`.
162
+ - Узел без `selector` — позиционный путь от корня области: `#`, `#/0`, `#/0/2`.
163
+ - Текст — позиционный путь первой части.
164
+ - `SchemaNodeId` — строка с брендом: в `compiled.nodes.get(...)` ключ приводят
165
+ (`'email' as SchemaNodeId`).
166
+
167
+ ### Области — `scope`
168
+
169
+ | Область | Идентификатор | Что в ней |
170
+ | ---------------- | ---------------------------- | --------------------------------------- |
171
+ | корень сборки | `#` | дерево `schema(model)` |
172
+ | подформа (`part`) | `#/registrationAddress` | содержимое части; путь группы от модели области |
173
+ | строка массива | `#/phones[<ключ>]` | разметка строки из `item(rowModel)` |
174
+ | вложенные | `#/coBorrowers[3]/phones[7]` | область внутри области |
175
+
176
+ - В скобках строки — **стабильный ключ** строки, а не индекс: он держится за элемент модели и не
177
+ меняется при `move` / `swap`. Тот же ключ отдаёт `FormArray.keyAt(index)`.
178
+ - **`selector` уникален в своей области.** Повтор в одной области — исключение при сборке с путями
179
+ обоих узлов. Селекторы строк массива и подформ живут в своих областях и с корнем не
180
+ пересекаются: одна часть, подключённая дважды, даёт две области без конфликта.
181
+ - По `selector` узел адресуют поведение (`schema.node(selector)` — в своей области) и шаги потока
182
+ (`flow.steps[].id` — `selector` узла шага в корневой области).
183
+
184
+ ### Подформы и строки
185
+
186
+ ) делит с ней область: правило к её узлу из поведения области работает.
187
+ - **Строка массива** компилируется при первом обращении: `arrayNode.row(rowModel)` — одна
188
+ компиляция на под-модель строки; форма строки и рендерер получают один и тот же объект. Узлы
189
+ строк в `nodes` корня не лежат.
190
+
191
+ ### Что компилятор проверяет
192
+
193
+ | Ситуация | Итог |
194
+ | --------------------------------------------------------- | ------------------------------------------------------ |
195
+ | повтор `selector` в одной области | исключение с путями обоих узлов |
196
+ | узел содержит сам себя | исключение — дерево зациклено |
197
+ | ключ узла, который сборка не читает (`value`, `array`, …) | dev-предупреждение с подсказкой замены (у строк массива — одно на шаблон) |
198
+ | узел без `component` и без `children` | dev-предупреждение: ничего не рисует; пустая секция — `children: []` |
199
+ | одна ручка модели в двух узлах | dev-предупреждение; нода берёт `component`, `componentProps` и `disabled` последней привязки |
200
+ | `item` на привязке не-массиве, `part` на не-группе | dev-предупреждение, узел становится контейнером |
201
+ | нераспознанная привязка (`model: model.email` без `$`) | dev-предупреждение, узел становится контейнером |
202
+ | ребёнок — группа модели или под-модель вместо узла | dev-предупреждение, ребёнок пропущен |
203
+ | `null` / `undefined` / `boolean` среди `children` | пропускается молча — условная вставка `cond && узел` |
204
+
205
+ Вложенные узлы читаются только из `children` и из поддерева `part`; в `componentProps` и под
206
+ произвольными ключами компилятор не заглядывает.
207
+
208
+ ## Examples
209
+
210
+ ### Компиляция и поиск узлов
211
+
212
+ ```typescript
213
+ import { compileSchema, createModel, type SchemaNodeId } from '@reformer/core';
214
+
215
+ const model = createModel({ email: '', phones: [{ number: '' }] });
216
+
217
+ const compiled = compileSchema(
218
+ {
219
+ children: [
220
+ { selector: 'email', model: model.$.email, component: Input },
221
+ { model: model.$.phones, item: (row) => ({ model: row.$.number, component: Input }) },
222
+ ],
223
+ },
224
+ model
225
+ );
226
+
227
+ compiled.nodes.get('email' as SchemaNodeId)?.kind; // 'field'
228
+ compiled.fields.get(model.$.email)?.component; // Input
229
+ compiled.arrays.get(model.$.phones)?.row(model.phones.at(0)!).scope; // '#/phones[<ключ строки>]'
230
+ compiled.nodes.get('#/1' as SchemaNodeId)?.kind; // 'array' — узел без selector: позиционный id
231
+ ```
232
+
233
+ ### Схема сборки — `bundle.render.tree`
234
+
235
+ ```typescript
236
+ const bundle = createForm<CreditForm>({ model: createCreditModel(), schema: creditSchema });
237
+
238
+ bundle.render.tree; // CompiledSchema корневой области; без schema — undefined
239
+ bundle.render.node('wizard'); // управление узлом корневой области по selector
240
+ ```
241
+
242
+ Своему рендереру и инструментам нужна именно эта компиляция: повторный `compileSchema` того же
243
+ дерева даёт другие объекты и другой кэш строк.
244
+
245
+ ### Обход дерева — список селекторов
246
+
247
+ ```typescript
248
+ import type { CompiledNode, CompiledSchema } from '@reformer/core';
249
+
250
+ function selectorsOf(schema: CompiledSchema): string[] {
251
+ const out: string[] = [];
252
+ const walk = (node: CompiledNode): void => {
253
+ if (node.selector !== undefined) out.push(`${node.scope} ${node.selector}`);
254
+ if (node.kind === 'container') {
255
+ for (const child of node.children) if (child.kind !== 'text') walk(child);
256
+ }
257
+ if (node.kind === 'part') walk(node.content.root); // часть — своя область
258
+ // строки массива — node.row(rowModel) для каждой под-модели строки
259
+ };
260
+ walk(schema.root);
261
+ return out;
262
+ }
263
+ ```
264
+
265
+ ### Свой рендерер — разбор по `kind`
266
+
267
+ ```tsx
268
+ function renderNode(node: CompiledChild): ReactNode {
269
+ switch (node.kind) {
270
+ case 'text':
271
+ return node.parts.map((part) => (typeof part === 'object' ? part.value : part)).join('');
272
+ case 'field':
273
+ return <Field key={node.id} handle={node.handle} component={node.component} props={node.props} />;
274
+ case 'array': // у каждой строки своя область: node.row(под-модель строки)
275
+ return Array.from({ length: node.control.length }, (_, i) =>
276
+ renderNode(node.row(node.control.at(i) as object).root)
277
+ );
278
+ case 'part':
279
+ return renderNode(node.content.root);
280
+ case 'container':
281
+ return node.children.map(renderNode);
282
+ }
283
+ }
284
+ ```
285
+
286
+ ## Anti-patterns
287
+
288
+ ```typescript
289
+ // ❌ Два узла с одним selector в корне — исключение при сборке
290
+ children: [
291
+ { selector: 'address', model: model.$.registrationAddress, part: address },
292
+ { selector: 'address', model: model.$.residenceAddress, part: address },
293
+ ];
294
+
295
+ // ✅ Разные селекторы; внутри части селекторы свои — повтор в двух частях не конфликтует
296
+ children: [
297
+ { selector: 'registration', model: model.$.registrationAddress, part: address },
298
+ { selector: 'residence', model: model.$.residenceAddress, part: address },
299
+ ];
300
+ ```
301
+
302
+ ```typescript
303
+ // ❌ Индекс строки в идентификаторе области: после move/swap он указывает на чужую строку
304
+ const scope = `#/phones[${index}]`;
305
+
306
+ // ✅ Ключ строки — FormArray.keyAt(index); он же стоит в скобках области строки
307
+ const scope = `#/phones[${form.phones.keyAt(index)}]`;
308
+ ```
309
+
310
+ ```typescript
311
+ // ❌ Узел строки массива ищется в nodes корня — его там нет
312
+ bundle.render.tree?.nodes.get('co-borrower' as SchemaNodeId); // undefined
313
+
314
+ // ✅ Узел строки — в схеме строки; в поведении — applyEach + schema.node(...) строки
315
+ const coBorrowers = bundle.render.tree?.arrays.get(model.$.coBorrowers);
316
+ coBorrowers?.row(model.coBorrowers.at(0)!).nodes.get('co-borrower' as SchemaNodeId);
317
+ ```
318
+
319
+ ```typescript
320
+ // ❌ Узлы в componentProps — компилятор туда не заглядывает, поле не появится
321
+ { component: Section, componentProps: { content: { model: model.$.email, component: Input } } }
322
+
323
+ // ✅ Вложенные узлы — только в children
324
+ { component: Section, children: [{ model: model.$.email, component: Input }] }
325
+ ```
326
+
327
+ ## See also
328
+
329
+ - [09-formschema.md](./09-formschema.md) — авторский формат узлов
330
+ - [35-unified-contract.md](./35-unified-contract.md) — одна схема, одна сборка, одно поведение
331
+ - [38-form-flow.md](./38-form-flow.md) — шаги потока адресуются `selector` узлов шагов
332
+ - [10-arrays.md](./10-arrays.md) — массивы под-форм и `keyAt`