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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +91 -73
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +370 -242
  4. package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +28 -7
  9. package/dist/form/behaviors/context.d.ts +19 -2
  10. package/dist/form/behaviors/index.d.ts +3 -3
  11. package/dist/form/behaviors/internals.d.ts +9 -2
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +6 -4
  15. package/dist/form/behaviors/types.d.ts +12 -4
  16. package/dist/form/create-form.d.ts +47 -35
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +45 -28
  19. package/dist/form/form-config-base.d.ts +46 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +16 -21
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +30 -14
  37. package/dist/form/signal-node-registry.d.ts +6 -7
  38. package/dist/form/submission.d.ts +93 -0
  39. package/dist/form/type-guards.d.ts +14 -73
  40. package/dist/form/types/contracts.d.ts +19 -92
  41. package/dist/form/types/field-config.d.ts +27 -0
  42. package/dist/form/types/index.d.ts +5 -57
  43. package/dist/form/types/nodes.d.ts +159 -0
  44. package/dist/form/types/schema-node.d.ts +139 -63
  45. package/dist/form/types/validation-schema.d.ts +23 -27
  46. package/dist/form/validation/context.d.ts +42 -7
  47. package/dist/form/validation/index.d.ts +9 -8
  48. package/dist/form/validation/operators.d.ts +22 -30
  49. package/dist/form/validation/run.d.ts +93 -18
  50. package/dist/form/validation/strategy.d.ts +41 -13
  51. package/dist/form/validation/types.d.ts +74 -20
  52. package/dist/form/validators/date-utils.d.ts +13 -1
  53. package/dist/form/validators/email.d.ts +8 -7
  54. package/dist/form/validators/file-type.d.ts +8 -7
  55. package/dist/form/validators/file-utils.d.ts +2 -1
  56. package/dist/form/validators/future-date.d.ts +8 -7
  57. package/dist/form/validators/index.d.ts +1 -1
  58. package/dist/form/validators/integer.d.ts +8 -7
  59. package/dist/form/validators/is-date.d.ts +11 -9
  60. package/dist/form/validators/is-number.d.ts +11 -9
  61. package/dist/form/validators/max-age.d.ts +13 -9
  62. package/dist/form/validators/max-date.d.ts +8 -7
  63. package/dist/form/validators/max-file-size.d.ts +8 -7
  64. package/dist/form/validators/max-files.d.ts +8 -7
  65. package/dist/form/validators/max-length.d.ts +14 -7
  66. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  67. package/dist/form/validators/max.d.ts +10 -8
  68. package/dist/form/validators/min-age.d.ts +12 -9
  69. package/dist/form/validators/min-date.d.ts +8 -7
  70. package/dist/form/validators/min-file-size.d.ts +8 -7
  71. package/dist/form/validators/min-files.d.ts +11 -9
  72. package/dist/form/validators/min-length.d.ts +14 -7
  73. package/dist/form/validators/min.d.ts +10 -8
  74. package/dist/form/validators/multiple-of.d.ts +10 -8
  75. package/dist/form/validators/non-negative.d.ts +8 -7
  76. package/dist/form/validators/non-zero.d.ts +8 -7
  77. package/dist/form/validators/past-date.d.ts +8 -7
  78. package/dist/form/validators/pattern.d.ts +9 -8
  79. package/dist/form/validators/phone.d.ts +8 -7
  80. package/dist/form/validators/required.d.ts +11 -9
  81. package/dist/form/validators/url.d.ts +20 -13
  82. package/dist/form/validators/validation-issue.d.ts +8 -0
  83. package/dist/i18n/descriptor.d.ts +1 -1
  84. package/dist/i18n/en.json.d.ts +5 -1
  85. package/dist/i18n/locale-store.d.ts +7 -0
  86. package/dist/i18n/message-format.d.ts +24 -4
  87. package/dist/i18n/ru.json.d.ts +5 -1
  88. package/dist/i18n/translator.d.ts +11 -0
  89. package/dist/i18n/validation-message.d.ts +42 -7
  90. package/dist/i18n.d.ts +7 -12
  91. package/dist/i18n.js +66 -283
  92. package/dist/index.d.ts +16 -7
  93. package/dist/index.js +924 -2855
  94. package/dist/loader-BWkigpVe.js +52 -0
  95. package/dist/locale/en.js +1 -1
  96. package/dist/locale/en.json +4 -1
  97. package/dist/locale/ru.js +4 -1
  98. package/dist/locale/ru.json +4 -1
  99. package/dist/locale-CG54AU2Q.js +219 -0
  100. package/dist/locale.js +1 -1
  101. package/dist/model/behaviors-value.d.ts +24 -6
  102. package/dist/model/create-model.d.ts +2 -2
  103. package/dist/model/derived-registry.d.ts +1 -1
  104. package/dist/model/index.d.ts +11 -5
  105. package/dist/model/model-nodes.d.ts +79 -12
  106. package/dist/model/model-signals-proxy.d.ts +5 -6
  107. package/dist/model/model-value-proxy.d.ts +20 -15
  108. package/dist/model/safe-effect.d.ts +33 -1
  109. package/dist/model/types.d.ts +63 -13
  110. package/dist/model.js +20 -18
  111. package/dist/node-kind-C-r6ZMdo.js +15 -0
  112. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  113. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  114. package/dist/platforms/react/form-bundle-context.d.ts +2 -2
  115. package/dist/platforms/react/hooks/types.d.ts +57 -38
  116. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  117. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  118. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  119. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  120. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  121. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  122. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  123. package/dist/platforms/react/i18n/context.d.ts +6 -1
  124. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  125. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  126. package/dist/platforms/react/index.d.ts +6 -4
  127. package/dist/react.d.ts +22 -0
  128. package/dist/react.js +505 -0
  129. package/dist/runtime-token.d.ts +1 -2
  130. package/dist/safe-effect-BbtX3KuN.js +643 -0
  131. package/dist/schema-controller-CpiO2JoK.js +143 -0
  132. package/dist/signals.d.ts +15 -9
  133. package/dist/signals.js +11 -6
  134. package/dist/strategy-CoFSemuP.js +375 -0
  135. package/dist/type-guards-D-N_5Ddh.js +19 -0
  136. package/dist/validation-issue-B9mjI8jz.js +7 -0
  137. package/dist/validation-message-Ced9MGxs.js +194 -0
  138. package/dist/validation.js +9 -9
  139. package/dist/validators/email.js +5 -5
  140. package/dist/validators/future-date.js +8 -8
  141. package/dist/validators/integer.js +3 -3
  142. package/dist/validators/is-date.js +4 -4
  143. package/dist/validators/is-number.js +4 -4
  144. package/dist/validators/max-age.js +8 -9
  145. package/dist/validators/max-date.js +8 -8
  146. package/dist/validators/max-length.js +7 -7
  147. package/dist/validators/max.js +4 -4
  148. package/dist/validators/min-age.js +10 -8
  149. package/dist/validators/min-date.js +3 -3
  150. package/dist/validators/min-length.js +5 -5
  151. package/dist/validators/min.js +4 -4
  152. package/dist/validators/multiple-of.js +11 -7
  153. package/dist/validators/non-negative.js +3 -3
  154. package/dist/validators/non-zero.js +3 -3
  155. package/dist/validators/past-date.js +8 -8
  156. package/dist/validators/pattern.js +5 -5
  157. package/dist/validators/phone.js +11 -9
  158. package/dist/validators/required.js +2 -2
  159. package/dist/validators/url.js +48 -12
  160. package/dist/validators.js +17 -17
  161. package/docs/llms/01-api-reference.md +60 -34
  162. package/docs/llms/02-quick-start.md +111 -80
  163. package/docs/llms/03-api-signatures.md +368 -153
  164. package/docs/llms/04-common-patterns.md +58 -56
  165. package/docs/llms/05-common-mistakes.md +69 -44
  166. package/docs/llms/06-troubleshooting.md +14 -9
  167. package/docs/llms/07-complete-import.md +74 -43
  168. package/docs/llms/08-form-types.md +11 -4
  169. package/docs/llms/09-formschema.md +129 -81
  170. package/docs/llms/10-arrays.md +106 -75
  171. package/docs/llms/11-async-watchfield.md +2 -2
  172. package/docs/llms/13-multi-step.md +134 -63
  173. package/docs/llms/14-extended-mistakes.md +20 -17
  174. package/docs/llms/15-project-structure.md +221 -74
  175. package/docs/llms/16-ui-components.md +55 -42
  176. package/docs/llms/17-nonexistent-api.md +199 -32
  177. package/docs/llms/18-conditional-fields.md +66 -32
  178. package/docs/llms/19-reading-values.md +15 -3
  179. package/docs/llms/20-compute-vs-watch.md +16 -10
  180. package/docs/llms/21-array-operations.md +51 -27
  181. package/docs/llms/22-cycle-detection.md +1 -1
  182. package/docs/llms/23-copy-from.md +2 -2
  183. package/docs/llms/24-sync-fields.md +6 -5
  184. package/docs/llms/25-reset-when.md +14 -7
  185. package/docs/llms/26-transform-value.md +4 -4
  186. package/docs/llms/27-revalidate-when.md +16 -15
  187. package/docs/llms/28-submit-and-reset.md +276 -128
  188. package/docs/llms/29-async-preload.md +39 -29
  189. package/docs/llms/30-type-safety-recipes.md +105 -51
  190. package/docs/llms/31-async-validator-debounce.md +96 -53
  191. package/docs/llms/33-validation-strategy.md +177 -65
  192. package/docs/llms/34-i18n.md +69 -43
  193. package/docs/llms/35-unified-contract.md +100 -39
  194. package/docs/llms/36-issue-messages.md +284 -0
  195. package/docs/llms/37-compiled-schema.md +332 -0
  196. package/docs/llms/38-form-flow.md +397 -0
  197. package/llms-index.json +1 -1
  198. package/llms.txt +6084 -3420
  199. package/package.json +23 -4
  200. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  201. package/dist/date-utils-dTT_x_Be.js +0 -31
  202. package/dist/form/create-core-form.d.ts +0 -72
  203. package/dist/form/factories/index.d.ts +0 -6
  204. package/dist/form/factories/node-factory.d.ts +0 -189
  205. package/dist/form/form-submitter.d.ts +0 -121
  206. package/dist/form/nodes/array-node.d.ts +0 -276
  207. package/dist/form/nodes/field-node.d.ts +0 -297
  208. package/dist/form/nodes/form-node.d.ts +0 -326
  209. package/dist/form/nodes/group-node.d.ts +0 -284
  210. package/dist/form/nodes/model-array-node.d.ts +0 -126
  211. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  212. package/dist/form/status-machine.d.ts +0 -115
  213. package/dist/form/types/deep-schema.d.ts +0 -107
  214. package/dist/form/types/form-proxy.d.ts +0 -141
  215. package/dist/form/unique-id.d.ts +0 -45
  216. package/dist/form/validation/config.d.ts +0 -73
  217. package/dist/form/validation/error-handler.d.ts +0 -166
  218. package/dist/form/validators/validation-error.d.ts +0 -8
  219. package/dist/index-BrNkQj1y.js +0 -105
  220. package/dist/loader-Dv_Hl7JC.js +0 -216
  221. package/dist/model-value-proxy-caTmAiUd.js +0 -419
  222. package/dist/node-BeV_lV5i.js +0 -100
  223. package/dist/strategy-DgzELWno.js +0 -204
  224. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,26 +1,41 @@
1
1
  import { FormValidationController } from '../../../form/validation/strategy';
2
- /** Минимум, который хук ожидает от бандла: опциональный контроллер живой валидации. */
2
+ /** Минимум, который хук ожидает от бандла: контроллер живой валидации и снятие — оба опционально. */
3
3
  export interface FormBundleLike {
4
4
  validation?: {
5
5
  controller: FormValidationController;
6
6
  };
7
+ /** Снять сборку: поведение, подписки нод, живую проверку. Есть у всех фабрик семейства. */
8
+ dispose?(): void;
7
9
  }
8
10
  /**
9
11
  * Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте
10
- * (не при SSR — там эффекты не выполняются).
12
+ * (не при SSR — там эффекты не выполняются); при размонтировании снять сборку.
11
13
  *
12
14
  * @typeParam B - Тип бандла конкретной фабрики.
13
- * @param factory - Фабрика бандла, обычно `() => createCoreForm({…})`.
15
+ * @param factory - Фабрика бандла, обычно `() => createForm({…})`. Сборкой владеет хук: при
16
+ * размонтировании он вызывает её `dispose()`, поэтому фабрика должна собирать НОВУЮ форму, а не
17
+ * возвращать чужую, живущую дольше компонента.
14
18
  * @returns Стабильный бандл.
15
19
  *
16
20
  * @example
17
21
  * ```tsx
18
- * const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCreditModel() }));
22
+ * const credit = useFormBundle(() => createForm<CreditForm>({ model: createCreditModel() }));
19
23
  * ```
20
24
  *
21
25
  * @remarks
22
26
  * Передавайте именно фабрику: `useState` трактует функцию как ленивый инициализатор, а некоторые
23
27
  * части бандла сами являются функциями (`RenderSchemaProxy`), поэтому `useFormBundle(bundle.render)`
24
28
  * вызвал бы её вместо сохранения.
29
+ *
30
+ * Сборка снимается микрозадачей после размонтирования, а не в самой очистке эффекта (см. шапку
31
+ * модуля): код, который сразу после размонтирования пишет в модель, ещё видит живое поведение.
32
+ *
33
+ * **Ограничение StrictMode (только разработка).** StrictMode вызывает ленивый инициализатор
34
+ * `useState` дважды, и фабрика на монтирование срабатывает два раза. Второй бандл React
35
+ * выбрасывает до коммита: хук его не видит, поэтому не армирует и не снимает. Живая проверка у
36
+ * него не запущена, но поведение (`compute`, `onChange`, …) работает, пока бандл не соберёт
37
+ * сборщик мусора. В production инициализатор вызывается один раз и утечки нет. Побочные эффекты
38
+ * на создание (запрос данных в фабрике или в `setup`) в разработке поэтому тоже срабатывают
39
+ * дважды — запускайте их из поведения формы или из эффекта компонента.
25
40
  */
26
41
  export declare function useFormBundle<B extends FormBundleLike>(factory: () => B): B;
@@ -0,0 +1,40 @@
1
+ import { FormFlow } from '../../../form/flow/flow';
2
+ /**
3
+ * Состояние потока на момент рендера.
4
+ *
5
+ * @group React
6
+ */
7
+ export interface FormFlowSnapshot {
8
+ /** Текущий шаг. */
9
+ readonly current: string;
10
+ /** Номер текущего шага с нуля. */
11
+ readonly index: number;
12
+ /** Пройденные шаги. */
13
+ readonly completed: ReadonlySet<string>;
14
+ readonly isFirst: boolean;
15
+ readonly isLast: boolean;
16
+ /** Идёт ли проверка шага. */
17
+ readonly validating: boolean;
18
+ }
19
+ /**
20
+ * Подписаться на поток шагов: компонент перерисовывается при смене шага, пройденных шагов и
21
+ * состояния проверки. Переходы — методами самого потока (`flow.next()`, `flow.goTo(id)`).
22
+ *
23
+ * @param flow - Поток шагов (`bundle.flow`).
24
+ * @returns {@link FormFlowSnapshot}.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function Steps({ flow }: { flow: FormFlow }) {
29
+ * const { current, isLast, validating } = useFormFlow(flow);
30
+ * return (
31
+ * <button disabled={isLast || validating} onClick={() => void flow.next()}>
32
+ * Далее ({current})
33
+ * </button>
34
+ * );
35
+ * }
36
+ * ```
37
+ *
38
+ * @group React
39
+ */
40
+ export declare function useFormFlow(flow: FormFlow): FormFlowSnapshot;
@@ -1,11 +1,11 @@
1
- import { FormModel } from '../../../index';
2
- import { ValidationSchema } from '../../../form/validation';
1
+ import { FormModel } from '../../../model/types';
2
+ import { ValidationDefinition } from '../../../form/validation';
3
3
  import { ValidationStrategyKind } from '../../../form/validation/strategy';
4
4
  export interface UseFormValidationArgs<T> {
5
5
  /** Модель данных формы. */
6
6
  model: FormModel<T>;
7
7
  /** Схема валидации. **Стабильная ссылка** (module-level `const` / `useMemo`) — иначе ломается дедуп раннера. */
8
- schema: ValidationSchema<T>;
8
+ schema: ValidationDefinition<T>;
9
9
  /** Стратегия запуска. Default `'submit'`. */
10
10
  strategy?: ValidationStrategyKind;
11
11
  /** Debounce (мс) для live-фаз (`change` / live-часть `afterFirstSubmit`). */
@@ -14,9 +14,15 @@ export interface UseFormValidationArgs<T> {
14
14
  liveAfterSubmit?: 'change' | 'blur';
15
15
  }
16
16
  export interface UseFormValidationResult {
17
- /** Полный прогон + раскрытие ошибок (`touch:true`); для `onSubmit`. Стабильная ссылка. */
17
+ /**
18
+ * Попытка отправки: полный прогон; при замечаниях раскрыта вся форма, `afterFirstSubmit`
19
+ * переходит в живую фазу. `true` — только статус `valid`. Для `onSubmit`. Стабильная ссылка.
20
+ */
18
21
  submit: () => Promise<boolean>;
19
- /** Алиас `submit` для не-submit сценариев. */
22
+ /**
23
+ * Проверка без отправки: полный прогон, раскрыты замечания проверенных полей, фаза
24
+ * `afterFirstSubmit` не меняется. Для кнопки «Проверить». Стабильная ссылка.
25
+ */
20
26
  validate: () => Promise<boolean>;
21
27
  /** Идёт ли прогон (submit или live) — для блокировки кнопки. */
22
28
  isValidating: boolean;
@@ -31,7 +37,7 @@ export interface UseFormValidationResult {
31
37
  *
32
38
  * @example
33
39
  * ```tsx
34
- * const validation = defineValidationSchema<Form>(({ model }) => { ... }); // module-level
40
+ * const validation = defineValidation<Form>(({ model }) => { ... }); // module-level
35
41
  *
36
42
  * function MyForm() {
37
43
  * const { submit, isValidating } = useFormValidation({
@@ -1,13 +1,13 @@
1
- import { ArrayNode } from '../../../form/nodes/array-node';
1
+ import { FormArray } from '../../../form/types/index';
2
2
  /**
3
3
  * React-хук для подписки только на длину массива.
4
4
  *
5
- * Оптимизированная версия {@link useFormControl} для ArrayNode, которая
6
- * подписывается только на сигнал `length`. Компонент не будет ре-рендериться
5
+ * Оптимизированная версия {@link useFormControl} для FormArray, которая
6
+ * подписывается только на длину массива. Компонент не будет ре-рендериться
7
7
  * при изменении значений вложенных полей.
8
8
  *
9
9
  * @typeParam T - Тип элемента массива
10
- * @param control - ArrayNode для подписки
10
+ * @param control - FormArray для подписки
11
11
  * @returns Текущая длина массива
12
12
  *
13
13
  * @example
@@ -18,7 +18,7 @@ import { ArrayNode } from '../../../form/nodes/array-node';
18
18
  * return (
19
19
  * <div>
20
20
  * {arrayNode.map((item, index) => (
21
- * <ItemRenderer key={item.id} item={item} />
21
+ * <ItemRenderer key={arrayNode.keyAt(index)} item={item} />
22
22
  * ))}
23
23
  * </div>
24
24
  * );
@@ -27,4 +27,4 @@ import { ArrayNode } from '../../../form/nodes/array-node';
27
27
  *
28
28
  * @group React Hooks
29
29
  */
30
- export declare function useArrayLength<T extends object>(control: ArrayNode<T>): number;
30
+ export declare function useArrayLength<T extends object>(control: FormArray<T>): number;
@@ -1,24 +1,22 @@
1
- import { FieldNode } from '../../../form/nodes/field-node';
2
- import { ArrayNode } from '../../../form/nodes/array-node';
3
- import { FormValue } from '../../../form/types/index';
1
+ import { FormArray, FormControl } from '../../../form/types/index';
4
2
  import { FieldControlState, ArrayControlState } from './types';
5
3
  /**
6
- * React-хук для подписки на состояние {@link ArrayNode}.
4
+ * React-хук для подписки на состояние {@link FormArray}.
7
5
  *
8
6
  * @typeParam T - Тип элемента массива
9
- * @param control - ArrayNode или undefined
7
+ * @param control - FormArray или undefined
10
8
  * @returns Состояние массива {@link ArrayControlState}
11
9
  *
12
10
  * @group React Hooks
13
11
  */
14
- export declare function useFormControl<T extends object>(control: ArrayNode<T> | undefined): ArrayControlState<T>;
12
+ export declare function useFormControl<T extends object>(control: FormArray<T> | undefined): ArrayControlState<T>;
15
13
  /**
16
- * React-хук для подписки на состояние {@link FieldNode}.
14
+ * React-хук для подписки на состояние {@link FormControl}.
17
15
  *
18
16
  * @typeParam T - Тип значения поля
19
- * @param control - FieldNode для подписки
17
+ * @param control - FormControl для подписки
20
18
  * @returns Состояние поля {@link FieldControlState}
21
19
  *
22
20
  * @group React Hooks
23
21
  */
24
- export declare function useFormControl<T extends FormValue>(control: FieldNode<T>): FieldControlState<T>;
22
+ export declare function useFormControl<T>(control: FormControl<T>): FieldControlState<T>;
@@ -1,11 +1,10 @@
1
- import { FieldNode } from '../../../form/nodes/field-node';
2
- import { FormValue } from '../../../form/types/index';
1
+ import { FormControl } from '../../../form/types/index';
3
2
  /**
4
3
  * React-хук для подписки только на значение поля.
5
4
  *
6
5
  * Оптимизированная версия {@link useFormControl}, которая подписывается
7
6
  * только на сигнал `value`. Компонент не будет ре-рендериться при изменении
8
- * `errors`, `touched`, `valid` и других свойств состояния.
7
+ * `issues`, `touched`, `valid` и других свойств состояния.
9
8
  *
10
9
  * ## Когда использовать
11
10
  *
@@ -16,21 +15,21 @@ import { FormValue } from '../../../form/types/index';
16
15
  *
17
16
  * ## Когда НЕ использовать
18
17
  *
19
- * Если компоненту нужны `errors`, `touched`, `disabled` или другие свойства -
18
+ * Если компоненту нужны `issues`, `touched`, `disabled` или другие свойства -
20
19
  * используйте {@link useFormControl}. Множественные подписки на один контрол
21
20
  * через разные хуки менее эффективны, чем одна подписка через `useFormControl`.
22
21
  *
23
22
  * @typeParam T - Тип значения поля
24
- * @param control - FieldNode для подписки на значение
23
+ * @param control - FormControl для подписки на значение
25
24
  * @returns Текущее значение поля
26
25
  *
27
26
  * @example Условный рендеринг секции
28
27
  * ```tsx
29
- * import { useFormControlValue } from '@reformer/core';
28
+ * import { useFormControlValue } from '@reformer/core/react';
30
29
  *
31
30
  * interface FormFields {
32
- * hasShipping: FieldNode<boolean>;
33
- * shippingAddress: GroupNode<AddressFields>;
31
+ * hasShipping: FormControl<boolean>;
32
+ * shippingAddress: FormGroup<AddressFields>;
34
33
  * }
35
34
  *
36
35
  * function ShippingSection({ form }: { form: FormFields }) {
@@ -53,8 +52,8 @@ import { FormValue } from '../../../form/types/index';
53
52
  * @example Динамические опции на основе другого поля
54
53
  * ```tsx
55
54
  * interface FormFields {
56
- * country: FieldNode<string>;
57
- * city: FieldNode<string>;
55
+ * country: FormControl<string>;
56
+ * city: FormControl<string>;
58
57
  * }
59
58
  *
60
59
  * function CitySelect({ form }: { form: FormFields }) {
@@ -93,14 +92,14 @@ import { FormValue } from '../../../form/types/index';
93
92
  *
94
93
  * // Хук вызывается на уровне компонента-строки (не в цикле — Rules of Hooks).
95
94
  * // Доступ к полям элемента — через Proxy: item.quantity, item.price.
96
- * function OrderRow({ item }: { item: FormProxy<OrderItem> }) {
95
+ * function OrderRow({ item }: { item: FormGroup<OrderItem> }) {
97
96
  * const quantity = useFormControlValue(item.quantity);
98
97
  * const price = useFormControlValue(item.price);
99
98
  *
100
99
  * return <span>Итого: ${(quantity * price).toFixed(2)}</span>;
101
100
  * }
102
101
  *
103
- * function OrderList({ items }: { items: ArrayNode<OrderItem> }) {
102
+ * function OrderList({ items }: { items: FormArray<OrderItem> }) {
104
103
  * return (
105
104
  * <div>
106
105
  * {items.map((item, index) => (
@@ -114,7 +113,7 @@ import { FormValue } from '../../../form/types/index';
114
113
  * @example Preview значения
115
114
  * ```tsx
116
115
  * interface MarkdownEditorProps {
117
- * control: FieldNode<string>;
116
+ * control: FormControl<string>;
118
117
  * }
119
118
  *
120
119
  * function MarkdownPreview({ control }: MarkdownEditorProps) {
@@ -133,8 +132,8 @@ import { FormValue } from '../../../form/types/index';
133
132
  *
134
133
  * // Основной редактор использует useFormControl для полного состояния
135
134
  * function MarkdownEditor({ control }: MarkdownEditorProps) {
136
- * const { value, shouldShowError, errors } = useFormControl(control);
137
- * const message = useValidationMessage(); // из '@reformer/core/i18n'
135
+ * const { value, shouldShowIssues, issues } = useFormControl(control);
136
+ * const message = useIssueMessage(); // из '@reformer/core/react'
138
137
  *
139
138
  * return (
140
139
  * <div className="editor-container">
@@ -142,7 +141,7 @@ import { FormValue } from '../../../form/types/index';
142
141
  * value={value}
143
142
  * onChange={e => control.setValue(e.target.value)}
144
143
  * />
145
- * {shouldShowError && <span className="error">{message(errors[0])}</span>}
144
+ * {shouldShowIssues && <span className="error">{message(issues[0])}</span>}
146
145
  *
147
146
  * {/* Preview обновляется только при изменении value *}
148
147
  * <MarkdownPreview control={control} />
@@ -153,7 +152,7 @@ import { FormValue } from '../../../form/types/index';
153
152
  *
154
153
  * @example Счётчик символов
155
154
  * ```tsx
156
- * function CharacterCounter({ control, max }: { control: FieldNode<string>; max: number }) {
155
+ * function CharacterCounter({ control, max }: { control: FormControl<string>; max: number }) {
157
156
  * const value = useFormControlValue(control);
158
157
  * const remaining = max - value.length;
159
158
  *
@@ -166,8 +165,8 @@ import { FormValue } from '../../../form/types/index';
166
165
  * ```
167
166
  *
168
167
  * @see {@link useFormControl} - для полного состояния поля
169
- * @see {@link FieldNode} - тип контрола поля
168
+ * @see {@link FormControl} - тип контрола поля
170
169
  *
171
170
  * @group React Hooks
172
171
  */
173
- export declare function useFormControlValue<T extends FormValue>(control: FieldNode<T>): T;
172
+ export declare function useFormControlValue<T>(control: FormControl<T>): T;
@@ -9,9 +9,17 @@ export type ExtractSignalValues<T extends SignalMap> = {
9
9
  export interface SignalConfig<K extends string> {
10
10
  key: K;
11
11
  useShallowArrayEqual?: boolean;
12
+ /**
13
+ * Сравнивать объект поверхностно: новый объект с теми же значениями (`updateComponentProps`
14
+ * теми же пропсами) снапшот не меняет.
15
+ */
16
+ useShallowObjectEqual?: boolean;
12
17
  }
13
18
  /**
14
- * Хук для подписки на набор сигналов с кешированием
19
+ * Хук для подписки на набор сигналов с кешированием.
20
+ *
21
+ * Подписка — только на сигналы из `configs`: их смена пересобирает снапшот. Остальные сигналы
22
+ * `signals` в снапшот попадают значением на момент сборки и сами компонент не будят.
15
23
  * @internal
16
24
  */
17
25
  export declare function useSignalSubscription<TSignals extends SignalMap, TSnapshot extends ExtractSignalValues<TSignals>>(signals: TSignals, configs: SignalConfig<Extract<keyof TSignals, string>>[], buildSnapshot: (values: ExtractSignalValues<TSignals>) => TSnapshot,
@@ -7,7 +7,12 @@ export interface I18nContextValue extends I18nHandle {
7
7
  readonly pending: boolean;
8
8
  /** Ошибка последней загрузки языка. */
9
9
  readonly error: unknown;
10
+ /**
11
+ * Повторить загрузку языка, которая упала (`error` задан). Без загрузчика и без ошибки ничего
12
+ * не делает.
13
+ */
14
+ readonly retry: () => void;
10
15
  }
11
16
  /** Значение контекста для локали: ручка плюс состояние загрузки. */
12
- export declare function contextValue(handle: I18nHandle, pending: boolean, error: unknown): I18nContextValue;
17
+ export declare function contextValue(handle: I18nHandle, pending: boolean, error: unknown, retry?: () => void): I18nContextValue;
13
18
  export declare const I18nContext: import('react').Context<I18nContextValue>;
@@ -1,6 +1,7 @@
1
- import { ValidationError } from '../../../form/types/contracts';
1
+ import { ValidationIssue } from '../../../form/types/contracts';
2
2
  import { Messages } from '../../../i18n/locale';
3
3
  import { MessageValues } from '../../../i18n/message-format';
4
+ import { IssueMessages } from '../../../i18n/validation-message';
4
5
  import { I18nContextValue } from './context';
5
6
  /**
6
7
  * Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние
@@ -40,20 +41,25 @@ export declare function useI18n(): I18nContextValue;
40
41
  */
41
42
  export declare function useMessages<D extends Messages>(defaults: D): (key: keyof D & string, values?: MessageValues) => string;
42
43
  /**
43
- * Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` →
44
- * словарь локали по коду → встроенный английский → код. Идентичность функции меняется вместе с
45
- * локалью, поэтому показанные ошибки переводятся при смене языка без повторной валидации.
44
+ * Резолвер текста замечания валидации: словарь формы → словарь активной локали по коду →
45
+ * встроенный английский → код. Идентичность функции меняется вместе с локалью и словарём,
46
+ * поэтому показанные замечания переводятся при смене языка без повторной валидации.
46
47
  *
47
- * `FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете ошибки
48
+ * `FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете замечания
48
49
  * своим компонентом.
49
50
  *
51
+ * @param messages - Словарь формы (`defineIssueMessages`). Стабильная ссылка — константа
52
+ * модуля, иначе резолвер будет пересоздаваться на каждый рендер.
53
+ *
50
54
  * @example
51
55
  * ```tsx
52
- * function FieldErrors({ control }: { control: FieldNode<string> }) {
53
- * const message = useValidationMessage();
54
- * const { errors } = useFormControl(control);
55
- * return <>{errors.map((error) => <span key={error.code}>{message(error)}</span>)}</>;
56
+ * const messages = defineIssueMessages({ 'phone.required': 'Укажите телефон' });
57
+ *
58
+ * function FieldIssues({ control }: { control: FormControl<string> }) {
59
+ * const message = useIssueMessage(messages);
60
+ * const { issues } = useFormControl(control);
61
+ * return <>{issues.map((issue) => <span key={issue.code}>{message(issue)}</span>)}</>;
56
62
  * }
57
63
  * ```
58
64
  */
59
- export declare function useValidationMessage(): (error: ValidationError) => string;
65
+ export declare function useIssueMessage(messages?: IssueMessages): (issue: ValidationIssue) => string;
@@ -11,12 +11,19 @@ export interface I18nStaticProviderProps {
11
11
  export interface I18nLoadingProviderProps {
12
12
  /** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */
13
13
  lang: string;
14
- /** Загрузчик локалей — обычно результат `createLocaleLoader`. */
14
+ /**
15
+ * Загрузчик локалей — обычно результат `createLocaleLoader`. Нужна СТАБИЛЬНАЯ ссылка (модульная
16
+ * константа, `useMemo`): новый загрузчик — новое хранилище, и язык запрашивается у него заново.
17
+ * На экране тем временем остаётся прежняя локаль, но инлайн-стрелка гоняла бы запрос на каждый
18
+ * рендер родителя.
19
+ */
15
20
  load: LocaleLoad;
16
21
  /**
17
22
  * Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр
18
23
  * мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`
19
- * до монтирования), первый рендер синхронный и `fallback` не показывается.
24
+ * до монтирования), первый рендер синхронный и `fallback` не показывается. Упала первая
25
+ * загрузка — дети показываются на встроенном английском, ошибка — в `useI18n().error`,
26
+ * повторить — `useI18n().retry()`.
20
27
  */
21
28
  fallback?: ReactNode;
22
29
  /** Отказ загрузки. Язык на экране при этом не меняется. */
@@ -2,13 +2,13 @@
2
2
  * Модуль `platforms/react` — биндинги ядра к React.
3
3
  *
4
4
  * Единственное место в пакете, где есть runtime-зависимость от `react` и
5
- * `use-sync-external-store`: слои `model` и `form` от React свободны (в `form/types/` остаются
6
- * только type-only `ComponentType`/`ElementType` — они стираются при компиляции). Хуки читают
5
+ * `use-sync-external-store`: слои `model` и `form` от React свободны и в рантайме, и в типах
6
+ * (компонент узла схемы для них — непрозрачная ссылка `ComponentRef`). Хуки читают
7
7
  * сигналы нод формы через `useSyncExternalStore` и отдают плоские снапшоты
8
8
  * ({@link FieldControlState}/{@link ArrayControlState}) в рендер.
9
9
  *
10
- * Barrel заведён как точка для будущего сабпата `@reformer/core/react`; сейчас его состав
11
- * реэкспортируется зонтиком `@reformer/core` — ровно теми же именами, что и до выноса.
10
+ * Состав barrel'а отдаёт вход `@reformer/core/react` (`src/react.ts`); корень `@reformer/core` его
11
+ * не реэкспортирует.
12
12
  *
13
13
  * @group Platforms
14
14
  * @module platforms/react
@@ -20,6 +20,8 @@ export { useFormValidation } from './hooks/use-form-validation';
20
20
  export type { UseFormValidationArgs, UseFormValidationResult } from './hooks/use-form-validation';
21
21
  export { useFormBundle } from './hooks/use-form-bundle';
22
22
  export type { FormBundleLike } from './hooks/use-form-bundle';
23
+ export { useFormFlow } from './hooks/use-form-flow';
24
+ export type { FormFlowSnapshot } from './hooks/use-form-flow';
23
25
  export { FormBundleContext, useFormBundleContext } from './form-bundle-context';
24
26
  export type { FieldControlState, ArrayControlState } from './hooks/types';
25
27
  export { getFieldAdapter, bindFieldProps, FIELD_WRAPPER_ONLY_PROPS, } from './field-binding/field-adapter';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Подпуть `@reformer/core/react` — привязка ядра к React: хуки нод формы, связка поля с контролом,
3
+ * контекст сборки и React-часть локализации.
4
+ *
5
+ * Единственный вход пакета с зависимостью от `react`. Корень `@reformer/core` и остальные сабпаты
6
+ * от React свободны, поэтому `react` и `react-dom` — необязательные peer-зависимости: они нужны
7
+ * только тому, кто импортирует этот вход.
8
+ *
9
+ * Контекст локализации обязан существовать в одном экземпляре на все пакеты `@reformer/*`:
10
+ * пакеты получают его отсюда как внешнюю зависимость и не включают в свою сборку
11
+ * (страж — `scripts/check-i18n-singleton.mjs`).
12
+ *
13
+ * @group Platforms
14
+ * @module react
15
+ * @mergeModuleWith <project>
16
+ */
17
+ export * from './platforms/react/index';
18
+ export { I18nProvider } from './platforms/react/i18n/provider';
19
+ export type { I18nProviderProps, I18nStaticProviderProps, I18nLoadingProviderProps, } from './platforms/react/i18n/provider';
20
+ export { useI18n, useMessages, useIssueMessage } from './platforms/react/i18n/hooks';
21
+ export { I18N_CONTEXT_MARKER } from './platforms/react/i18n/context';
22
+ export type { I18nContextValue } from './platforms/react/i18n/context';