@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,6 +1,6 @@
1
1
  import { Signal } from '@preact/signals-core';
2
2
  import { PathAwareSignal } from './types';
3
- import { ModelNode, GroupNode, ArrayNode } from './model-nodes';
3
+ import { ModelNode, ModelGroupNode, ModelArrayNode } from './model-nodes';
4
4
  /**
5
5
  * Узел дерева `$`: лист → сам {@link PathAwareSignal}, группа/массив → контейнерный узел —
6
6
  * {@link ReadonlySignal} агрегированного значения ПЛЮС доступ к детям по имени/индексу.
@@ -26,7 +26,7 @@ export declare function signalsProxy(node: ModelNode): any;
26
26
  *
27
27
  * @internal
28
28
  */
29
- export declare function containerNodeOf(handle: unknown): GroupNode | ArrayNode | undefined;
29
+ export declare function containerNodeOf(handle: unknown): ModelGroupNode | ModelArrayNode | undefined;
30
30
  /**
31
31
  * Узел дерева `model.$` — контейнер (группа/массив), а не лист?
32
32
  *
@@ -44,12 +44,11 @@ export declare function isModelContainerSignal(value: unknown): boolean;
44
44
  /** Узел-массив дерева `model.$` — ручка значения массива целиком. */
45
45
  export declare function isModelArraySignal(value: unknown): boolean;
46
46
  /**
47
- * «Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив
47
+ * «Ручка значения», к которой можно привязать поле: лист (сигнал, не контейнер) либо узел-массив
48
48
  * дерева `model.$` (значение — массив целиком, `.value` читается и пишется).
49
49
  *
50
50
  * Группа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой
51
- * `createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —
52
- * вместо голого `value instanceof Signal`, который узел-массив не проходит.
51
+ * `createForm` и рендереры отличают узел-поле схемы (`{ model, component }`) от контейнера.
53
52
  *
54
53
  * @group Model
55
54
  * @example
@@ -61,4 +60,4 @@ export declare function isModelArraySignal(value: unknown): boolean;
61
60
  * ```
62
61
  */
63
62
  export declare function isValueSignal(value: unknown): value is Signal<unknown>;
64
- export declare function resolveSignalAt(root: GroupNode, path: string): PathAwareSignal<unknown> | undefined;
63
+ export declare function resolveSignalAt(root: ModelGroupNode, path: string): PathAwareSignal<unknown> | undefined;
@@ -1,7 +1,7 @@
1
- import { GroupNode } from './model-nodes';
1
+ import { ModelGroupNode } from './model-nodes';
2
2
  import { FormModel, ModelArray } from './types';
3
- export declare const rootByFacade: WeakMap<object, GroupNode>;
4
- export declare function makeFormModel(group: GroupNode): any;
3
+ export declare const rootByFacade: WeakMap<object, ModelGroupNode>;
4
+ export declare function makeFormModel(group: ModelGroupNode): any;
5
5
  /**
6
6
  * Value-фасад для ручки {@link modelOf}: массив → {@link ModelArray}, объект → под-модель
7
7
  * {@link FormModel}.
@@ -39,13 +39,6 @@ export type ModelOf<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelAr
39
39
  export declare function modelOf<V extends object | null | undefined>(handle: {
40
40
  peek(): V;
41
41
  }): ModelOf<V>;
42
- /**
43
- * Ручка `$` по value-фасаду: под-модель → её `model.$`, фасад массива → узел-массив дерева `$`.
44
- * Для всего остального — `undefined`.
45
- *
46
- * @internal
47
- */
48
- export declare function signalsOf(facade: unknown): object | undefined;
49
42
  /**
50
43
  * Ручка массива по привязке: сама ручка `model.$.<массив>` либо value-фасад `model.<массив>`.
51
44
  * Для всего остального — `undefined`.
@@ -61,9 +54,10 @@ export declare function arrayHandleOf(binding: unknown): object | undefined;
61
54
  */
62
55
  export declare function groupHandleOf(binding: unknown): object | undefined;
63
56
  /**
64
- * Запасной шаблон нового элемента массива — `initialValue` узла-массива схемы. Действует, только
65
- * если модель своего шаблона не объявила (`arrayOf`): так живут формы, чья модель строится из
66
- * данных без кода.
57
+ * Запасной шаблон нового элемента массива — от того, кто строит модель извне: сборка формы
58
+ * передаёт сюда `initialValue` узла-массива. Действует, только если модель своего шаблона не
59
+ * объявила (`arrayOf`): так живут формы, чья модель строится из данных без кода. Что это узел
60
+ * схемы, модель не знает.
67
61
  *
68
62
  * @internal
69
63
  */
@@ -71,9 +65,20 @@ export declare function provideArrayBlank(handle: unknown, initialValue: unknown
71
65
  /**
72
66
  * Value-фасад модели (под-модель или фасад массива), а не обычный объект?
73
67
  *
74
- * Обходчикам чужих деревьев (схема формы) в фасад спускаться нельзя: чтение его ключей — это
75
- * реактивное чтение значений модели.
68
+ * Обходчикам чужих деревьев, в которых могут лежать фасады модели, в фасад спускаться нельзя:
69
+ * чтение его ключей — это реактивное чтение значений модели.
76
70
  *
77
71
  * @internal
78
72
  */
73
+ /**
74
+ * Точка отсчёта модели по ручке, к которой привязана нода формы: сигналу листа или фасаду
75
+ * массива. К ней сброс формы (`form.reset()`) возвращает ноду — к тому же, к чему `model.reset()`
76
+ * возвращает модель: последний `captureInitial` либо значение создания модели.
77
+ *
78
+ * `undefined` — ручка не из модели (нода над собственным сигналом): сбрасывать её не к чему,
79
+ * кроме значения, с которым она создана. @internal
80
+ */
81
+ export declare function referenceOf(handle: unknown): {
82
+ readonly value: unknown;
83
+ } | undefined;
79
84
  export declare function isModelFacade(value: unknown): boolean;
@@ -68,6 +68,38 @@ export declare function safeCallback<TArgs extends unknown[]>(callback: (...args
68
68
  * ```
69
69
  */
70
70
  export declare function runOutsideEffect(fn: () => void | Promise<void>): () => void;
71
+ /**
72
+ * Дождаться, пока отработают отложенные записи текущего тика — вместе с теми, что они породили
73
+ * (цепочка расчётов: каждая запись будит следующий расчёт, а тот снова пишет отложенно).
74
+ *
75
+ * Ждёт только микротаски: запись после таймера (debounce) или после `await` в async-callback сюда не
76
+ * входит — это уже не «тот же тик».
77
+ *
78
+ * Счётчик общий для рантайма, а не для одной модели: отложенные записи ставит {@link runOutsideEffect},
79
+ * а он моделей не различает. Нужен проверкам «по действию» — отправке и переходу шага: правила должны
80
+ * увидеть модель после записей поведения того же тика (колбэк `onChange`, сброс `resetOnDisable`).
81
+ *
82
+ * @returns Промис ожидания; `undefined` — отложенных записей сейчас нет, ждать нечего.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * model.loanType = 'mortgage'; // поведение допишет зависимые поля на микротаске
87
+ * await deferredWritesSettled();
88
+ * const values = model.get(); // уже с записями поведения
89
+ * ```
90
+ */
91
+ export declare function deferredWritesSettled(): Promise<void> | undefined;
92
+ /**
93
+ * Отложенные записи одной операции: задание выполняется на микротаске ({@link runOutsideEffect}),
94
+ * а `cancelAll` отменяет все ещё не выполненные — при снятии операции (запись после cleanup не
95
+ * случится) или когда новое задание делает ожидающие устаревшими (побеждает последнее).
96
+ *
97
+ * @internal
98
+ */
99
+ export declare function deferredWrites(): {
100
+ schedule(job: () => void): void;
101
+ cancelAll(): void;
102
+ };
71
103
  /**
72
104
  * Создает версию callback с поддержкой debounce, безопасную для effect
73
105
  *
@@ -76,7 +108,7 @@ export declare function runOutsideEffect(fn: () => void | Promise<void>): () =>
76
108
  * async-rejection) маршрутизируются dev-логгером (logDeferError).
77
109
  *
78
110
  * @param callback - Функция для выполнения
79
- * @param withDebounce - Функция debounce обёртки из BehaviorRegistry
111
+ * @param withDebounce - Обёртка, откладывающая вызов (debounce)
80
112
  * @returns Обёрнутая функция
81
113
  *
82
114
  * @example
@@ -14,6 +14,33 @@ export type PathAwareSignal<T> = Signal<T> & {
14
14
  * @internal
15
15
  */
16
16
  type Opaque = Date | File | Blob;
17
+ /**
18
+ * Значение одного ключа в {@link DeepPartial}: вложенная группа — частично, массив и спец-объект
19
+ * (Date/File/Blob) — целиком.
20
+ * @internal
21
+ */
22
+ type DeepPartialValue<V> = NonNullable<V> extends ReadonlyArray<unknown> | Opaque ? V : NonNullable<V> extends object ? DeepPartial<NonNullable<V>> | Extract<V, null | undefined> : V;
23
+ /**
24
+ * Частичное значение модели для {@link ModelApi.patch}: любой ключ можно опустить на любой
25
+ * глубине вложенных групп. Массив передаётся целиком — он заменяется, а не сливается.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * type Profile = { name: string; address: { city: string; street: string }; tags: string[] };
30
+ * const update: DeepPartial<Profile> = { address: { city: 'Казань' } };
31
+ * ```
32
+ *
33
+ * @group Model
34
+ */
35
+ export type DeepPartial<T> = {
36
+ [K in keyof T]?: DeepPartialValue<T[K]>;
37
+ };
38
+ /**
39
+ * `null` входит в тип `V`. Без `strictNullChecks` у потребителя `null` подходит любому типу —
40
+ * тогда признак ложен, и тип контейнера остаётся фасадом, как без `null`.
41
+ * @internal
42
+ */
43
+ type IncludesNull<V> = null extends string ? false : null extends V ? true : false;
17
44
  /**
18
45
  * Фасад элемента массива при обходе (`at`/`map`/`forEach`): массив → {@link ModelArray},
19
46
  * Opaque (Date/File/Blob) и примитив → значение, объект → под-модель {@link FormModel}.
@@ -21,7 +48,7 @@ type Opaque = Date | File | Blob;
21
48
  * чтобы `files: File[]`/`dates: Date[]`/вложенные массивы не мис-типизировались как `FormModel`.
22
49
  * @internal
23
50
  */
24
- type ModelArrayItem<U> = NonNullable<U> extends ReadonlyArray<infer E> ? ModelArray<E> : NonNullable<U> extends Opaque ? U : NonNullable<U> extends object ? FormModel<NonNullable<U>> : U;
51
+ type ModelArrayItem<U> = IncludesNull<U> extends true ? U : NonNullable<U> extends ReadonlyArray<infer E> ? ModelArray<E> : NonNullable<U> extends Opaque ? U : NonNullable<U> extends object ? FormModel<NonNullable<U>> : U;
25
52
  /**
26
53
  * Значение поля в value-доступе модели:
27
54
  * - массив → {@link ModelArray}
@@ -29,10 +56,13 @@ type ModelArrayItem<U> = NonNullable<U> extends ReadonlyArray<infer E> ? ModelAr
29
56
  * - объект → под-модель {@link FormModel} (value-доступ + `.$`-сигналы + API get/set/patch/…);
30
57
  * промоутится рантаймом (`makeFormModel`); сигналы идентичны `model.$.<path>`
31
58
  * - примитив → значение
59
+ * - тип с `null` (`File[] | null`, `Address | null`) → значение как есть, без фасада: поле,
60
+ * которое бывает `null`, начинают с `null`, а такое поле в рантайме — лист (значение целиком).
61
+ * Контейнер, созданный из `[]` или `{}`, стал бы фасадом — nullable-контейнер начинают с `null`.
32
62
  *
33
63
  * @group Model
34
64
  */
35
- export type ModelValue<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArray<U> : NonNullable<V> extends Opaque ? V : NonNullable<V> extends object ? FormModel<NonNullable<V>> : V;
65
+ export type ModelValue<V> = IncludesNull<V> extends true ? V : NonNullable<V> extends ReadonlyArray<infer U> ? ModelArray<U> : NonNullable<V> extends Opaque ? V : NonNullable<V> extends object ? FormModel<NonNullable<V>> : V;
36
66
  /**
37
67
  * Карта value-полей объекта (value-половина {@link FormModel}): поля доступны как обычные свойства
38
68
  * (чтение реактивно внутри `effect`/`computed`, запись — присваиванием). Вложенные объекты-поля
@@ -80,6 +110,8 @@ export interface ModelArray<U> {
80
110
  forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
81
111
  /** Снимок массива значений (без подписки). */
82
112
  toArray(): U[];
113
+ /** Обход `for…of` / spread — те же элементы, что у {@link forEach}. */
114
+ [Symbol.iterator](): IterableIterator<ModelArrayItem<U>>;
83
115
  /** Индексный value-доступ. */
84
116
  [index: number]: ModelValue<U>;
85
117
  }
@@ -115,13 +147,13 @@ export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>
115
147
  * push/removeAt/move).
116
148
  *
117
149
  * В отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это
118
- * делает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),
150
+ * делает его {@link PathAwareSignal} — к нему привязывают поле (`{ model: model.$.tags, component }`),
119
151
  * правило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением
120
152
  * поля: мультивыбор, теги, список файлов.
121
153
  *
122
- * `V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`
123
- * в рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих
124
- * случаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).
154
+ * `V` — объявленный тип поля, включая `undefined`. Поле с `null` в типе (`string[] | null`) — не
155
+ * узел-массив, а лист {@link PathAwareSignal}: его начинают с `null`, и `length` с индексами у него
156
+ * в рантайме нет (см. {@link ModelValue}).
125
157
  *
126
158
  * @group Model
127
159
  */
@@ -135,10 +167,11 @@ export type ModelArraySignals<U, V> = Signal<V> & {
135
167
  * Узел дерева сигналов `model.$`: массив → {@link ModelArraySignals}, Opaque (Date/File/Blob)
136
168
  * и примитив → {@link PathAwareSignal}, объект → {@link ModelGroupSignals}. Для примитивных
137
169
  * и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
138
- * `PathAwareSignal<string>`, а не `ModelGroupSignals<never>`.
170
+ * `PathAwareSignal<string>`, а не `ModelGroupSignals<never>`. Тип с `null` — лист
171
+ * {@link PathAwareSignal}: такое поле начинают с `null`, и под-сигналов у него нет.
139
172
  * @internal
140
173
  */
141
- type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U, V> : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelGroupSignals<NonNullable<V>> : PathAwareSignal<V>;
174
+ type ModelSignalNode<V> = IncludesNull<V> extends true ? PathAwareSignal<V> : NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U, V> : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelGroupSignals<NonNullable<V>> : PathAwareSignal<V>;
142
175
  /**
143
176
  * API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
144
177
  *
@@ -159,18 +192,35 @@ export interface ModelApi<T> {
159
192
  * Полная установка значений: принимает объект целиком (все ключи `T`) и записывает их в модель.
160
193
  * Производными полями (цели `compute`) владеет compute — их значения из payload игнорируются.
161
194
  * Не меняет initial-снимок. Для частичного обновления (только переданные ключи) — {@link ModelApi.patch}.
195
+ *
196
+ * Если в объекте не хватает ключей (значение пришло без типа — например, с сервера), эти поля
197
+ * остаются прежними, а в dev выводится предупреждение со списком.
162
198
  */
163
199
  set(value: T): void;
164
200
  /**
165
201
  * Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
166
- * отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
202
+ * отсутствующие ключи НЕ трогаются. Вложенные группы сливаются так же, массив заменяется
203
+ * целиком. Не меняет initial-снимок.
204
+ */
205
+ patch(value: DeepPartial<T>): void;
206
+ /**
207
+ * Отличаются ли текущие значения от initial-снимка (value-diff). Производные поля — цели
208
+ * `compute` / `computeFrom` и поля с `markDerived` — не в счёт: их пишет поведение, а не
209
+ * пользователь, и форма без единой правки изменённой не считается.
167
210
  */
168
- patch(value: Partial<T>): void;
169
- /** Отличаются ли текущие значения от initial-снимка (value-diff). */
170
211
  isDirty(): boolean;
171
- /** Сбросить значения к initial-снимку. */
212
+ /**
213
+ * Сбросить значения к initial-снимку. Производные поля сброс не пишет, как `set` и `patch`:
214
+ * цель `compute` пересчитает поведение, когда вернутся её источники.
215
+ *
216
+ * Ноды формы не трогает: замечания погаснут при следующей проверке. Сброс для пользователя —
217
+ * `form.reset()`: та же точка отсчёта и чистые ноды.
218
+ */
172
219
  reset(): void;
173
- /** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
220
+ /**
221
+ * Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). К нему возвращают
222
+ * и `model.reset()`, и `form.reset()`.
223
+ */
174
224
  captureInitial(): void;
175
225
  /**
176
226
  * Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком
package/dist/model.js CHANGED
@@ -1,25 +1,27 @@
1
- import { c as s, e as r, a as l } from "./create-model-BBRHhHwK.js";
2
- import { a as o, d as c, i as d, b as n, c as t, m as f, u as m } from "./model-value-proxy-caTmAiUd.js";
3
- import { c as b, a as h, r as p, b as g, d as k, e as v, f as D, s as F, t as S, w as x } from "./behaviors-value-C7dSEhCc.js";
1
+ import { c as s, e as r, a as l } from "./create-model-BvcbNPgL.js";
2
+ import { a as d, g as o, f as t, d as c, i as f, b as n, c as m, m as u, r as p, s as b, e as g, u as h } from "./safe-effect-BbtX3KuN.js";
3
+ import { c as k, a as v, r as D, b as F, s as x, t as y, w as C } from "./behaviors-value-HA_Q03hc.js";
4
4
  export {
5
- o as arrayOf,
6
- b as computeFrom,
7
- h as copyFrom,
5
+ d as arrayOf,
6
+ k as computeFrom,
7
+ v as copyFrom,
8
8
  s as createModel,
9
+ o as deepEqual,
10
+ t as deferredWritesSettled,
9
11
  r as eachLeafSignal,
10
12
  l as eachValueSignal,
11
13
  c as isDerived,
12
- d as isModelContainerSignal,
14
+ f as isModelContainerSignal,
13
15
  n as isValueSignal,
14
- t as markDerived,
15
- f as modelOf,
16
- p as resetWhen,
17
- g as revalidateWhen,
18
- k as runOutsideEffect,
19
- v as safeCallback,
20
- D as safeDebouncedCallback,
21
- F as syncFields,
22
- S as transformValue,
23
- m as unmarkDerived,
24
- x as watchField
16
+ m as markDerived,
17
+ u as modelOf,
18
+ D as resetWhen,
19
+ F as revalidateWhen,
20
+ p as runOutsideEffect,
21
+ b as safeCallback,
22
+ g as safeDebouncedCallback,
23
+ x as syncFields,
24
+ y as transformValue,
25
+ h as unmarkDerived,
26
+ C as watchField
25
27
  };
@@ -0,0 +1,15 @@
1
+ const n = Symbol.for("@reformer/core/form-node-kind"), e = Symbol.for("@reformer/core/form-node-raw");
2
+ function t(r) {
3
+ if (typeof r != "object" || r === null) return;
4
+ const o = r[n];
5
+ return o === "control" || o === "group" || o === "array" ? o : void 0;
6
+ }
7
+ function f(r) {
8
+ return r[e] ?? r;
9
+ }
10
+ export {
11
+ n as N,
12
+ e as R,
13
+ t as k,
14
+ f as r
15
+ };
@@ -41,7 +41,7 @@ export interface FieldAdapter {
41
41
  strip?: string[];
42
42
  /**
43
43
  * Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:
44
- * errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает
44
+ * issues/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает
45
45
  * `useFormControl(control)`.
46
46
  */
47
47
  passControl?: boolean;
@@ -43,8 +43,11 @@ export declare function makeElementFieldHandle(el: RefObject<HTMLElement | null>
43
43
  export declare function useFieldHandleRef<H = FieldHandle>(outerRef: Ref<H> | undefined): RefObject<unknown>;
44
44
  /**
45
45
  * Что публиковать потребителю по ref контрола: handle композита как есть, иначе (DOM-узел или
46
- * ещё пусто) — базовый {@link FieldHandle}, читающий узел лениво из `inner`.
46
+ * ещё пусто) — базовый {@link FieldHandle}, читающий узел лениво из `inner`. С `cache` базовый
47
+ * handle создаётся один раз и дальше переиспользуется.
47
48
  *
48
49
  * @internal Экспортируется для юнит-тестов.
49
50
  */
50
- export declare function resolveFieldHandle(inner: RefObject<unknown>): unknown;
51
+ export declare function resolveFieldHandle(inner: RefObject<unknown>, cache?: {
52
+ current: FieldHandle | null;
53
+ }): unknown;
@@ -12,7 +12,7 @@ import { FormBundle } from '../../form/form-bundle';
12
12
  export declare const FormBundleContext: import('react').Context<FormBundle<any> | null>;
13
13
  /**
14
14
  * Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера
15
- * (например, визард в разметке React-руками получает форму и валидацию пропсами).
15
+ * (например, визард в разметке React-руками получает форму и поток шагов пропсами).
16
16
  *
17
17
  * @typeParam T - Форма данных модели.
18
18
  *
@@ -20,7 +20,7 @@ export declare const FormBundleContext: import('react').Context<FormBundle<any>
20
20
  * ```tsx
21
21
  * function StepFooter() {
22
22
  * const bundle = useFormBundleContext<CreditForm>();
23
- * return <button onClick={() => bundle?.validation?.validateAll()}>Проверить</button>;
23
+ * return <button onClick={() => void bundle?.validate()}>Проверить</button>;
24
24
  * }
25
25
  * ```
26
26
  */
@@ -1,6 +1,6 @@
1
- import { ValidationError } from '../../../form/types/index';
1
+ import { ValidationIssue } from '../../../form/types/index';
2
2
  /**
3
- * Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FieldNode}.
3
+ * Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FormControl}.
4
4
  *
5
5
  * Содержит реактивные данные поля: значение, состояние валидации, флаги взаимодействия
6
6
  * и пользовательские props для компонентов.
@@ -10,12 +10,12 @@ import { ValidationError } from '../../../form/types/index';
10
10
  * @example Базовое использование
11
11
  * ```tsx
12
12
  * interface Props {
13
- * control: FieldNode<string>;
13
+ * control: FormControl<string>;
14
14
  * }
15
15
  *
16
16
  * function TextField({ control }: Props) {
17
17
  * const state = useFormControl(control);
18
- * const message = useValidationMessage(); // из '@reformer/core/i18n'
18
+ * const message = useIssueMessage(); // из '@reformer/core/react'
19
19
  *
20
20
  * return (
21
21
  * <div>
@@ -24,8 +24,8 @@ import { ValidationError } from '../../../form/types/index';
24
24
  * disabled={state.disabled}
25
25
  * onChange={e => control.setValue(e.target.value)}
26
26
  * />
27
- * {state.shouldShowError && state.errors[0] && (
28
- * <span className="error">{message(state.errors[0])}</span>
27
+ * {state.shouldShowIssues && state.issues[0] && (
28
+ * <span className="error">{message(state.issues[0])}</span>
29
29
  * )}
30
30
  * </div>
31
31
  * );
@@ -89,22 +89,22 @@ export interface FieldControlState<T> {
89
89
  *
90
90
  * @example
91
91
  * ```tsx
92
- * const { errors } = useFormControl(field);
93
- * const message = useValidationMessage(); // из '@reformer/core/i18n'
92
+ * const { issues } = useFormControl(field);
93
+ * const message = useIssueMessage(); // из '@reformer/core/react'
94
94
  *
95
95
  * return (
96
96
  * <ul className="error-list">
97
- * {errors.map((error, i) => (
97
+ * {issues.map((error, i) => (
98
98
  * <li key={i}>{message(error)}</li>
99
99
  * ))}
100
100
  * </ul>
101
101
  * );
102
102
  * ```
103
103
  */
104
- errors: ValidationError[];
104
+ issues: ValidationIssue[];
105
105
  /**
106
106
  * Флаг валидности поля.
107
- * `true` когда поле прошло все валидации (errors.length === 0).
107
+ * `true` когда поле прошло все валидации (issues.length === 0).
108
108
  *
109
109
  * @example
110
110
  * ```tsx
@@ -118,7 +118,7 @@ export interface FieldControlState<T> {
118
118
  valid: boolean;
119
119
  /**
120
120
  * Флаг невалидности поля.
121
- * `true` когда есть ошибки валидации (errors.length > 0).
121
+ * `true` когда есть ошибки валидации (issues.length > 0).
122
122
  * Противоположность {@link valid}.
123
123
  *
124
124
  * @example
@@ -148,28 +148,44 @@ export interface FieldControlState<T> {
148
148
  */
149
149
  touched: boolean;
150
150
  /**
151
- * Флаг для отображения ошибки.
152
- * Комбинация touched && invalid - удобный shortcut для UI.
151
+ * Показывать ли состояние ошибки — `aria-invalid`, рамку: поле невалидно и его трогали или
152
+ * меняли. Предупреждение (`severity: 'warning'`) поле невалидным не делает, поэтому для текстов
153
+ * замечаний есть {@link FieldControlState.shouldShowIssues}.
153
154
  *
154
155
  * @example
155
156
  * ```tsx
156
- * const { shouldShowError, errors } = useFormControl(field);
157
- * const message = useValidationMessage(); // из '@reformer/core/i18n'
157
+ * const { shouldShowError } = useFormControl(field);
158
+ *
159
+ * return <input {...props} aria-invalid={shouldShowError} />;
160
+ * ```
161
+ */
162
+ shouldShowError: boolean;
163
+ /**
164
+ * Показывать ли тексты замечаний: у поля есть замечания — ошибки или предупреждения, — и его
165
+ * трогали или меняли.
166
+ *
167
+ * @example
168
+ * ```tsx
169
+ * const { shouldShowError, shouldShowIssues, issues } = useFormControl(field);
170
+ * const message = useIssueMessage(); // из '@reformer/core/react'
158
171
  *
159
172
  * return (
160
173
  * <div>
161
- * <input {...props} />
162
- * {shouldShowError && (
163
- * <span className="error">{message(errors[0])}</span>
164
- * )}
174
+ * <input {...props} aria-invalid={shouldShowError} />
175
+ * {shouldShowIssues &&
176
+ * issues.map((issue) => (
177
+ * <span key={issue.code} className={issue.severity === 'warning' ? 'warning' : 'error'}>
178
+ * {message(issue)}
179
+ * </span>
180
+ * ))}
165
181
  * </div>
166
182
  * );
167
183
  * ```
168
184
  */
169
- shouldShowError: boolean;
185
+ shouldShowIssues: boolean;
170
186
  /**
171
187
  * Пользовательские props для передачи в UI-компоненты.
172
- * Устанавливаются через {@link FieldNode.updateComponentProps}.
188
+ * Устанавливаются через {@link FormControl.updateComponentProps}.
173
189
  *
174
190
  * @example
175
191
  * ```tsx
@@ -210,7 +226,7 @@ export interface FieldControlState<T> {
210
226
  dirty: boolean;
211
227
  }
212
228
  /**
213
- * Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.
229
+ * Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link FormArray}.
214
230
  *
215
231
  * Содержит реактивные данные массива: значения элементов, длину, состояние валидации
216
232
  * и флаги взаимодействия.
@@ -225,7 +241,7 @@ export interface FieldControlState<T> {
225
241
  * }
226
242
  *
227
243
  * interface Props {
228
- * control: ArrayNode<Phone>;
244
+ * control: FormArray<Phone>;
229
245
  * }
230
246
  *
231
247
  * function PhoneList({ control }: Props) {
@@ -235,7 +251,7 @@ export interface FieldControlState<T> {
235
251
  * <div>
236
252
  * {control.map((item, index) => (
237
253
  * <PhoneItem
238
- * key={item.id}
254
+ * key={control.keyAt(index)}
239
255
  * control={item}
240
256
  * onRemove={() => control.removeAt(index)}
241
257
  * />
@@ -247,7 +263,7 @@ export interface FieldControlState<T> {
247
263
  * Add Phone
248
264
  * </button>
249
265
  *
250
- * {!valid && <p className="error">Please fix phone errors</p>}
266
+ * {!valid && <p className="error">Please fix phone issues</p>}
251
267
  * </div>
252
268
  * );
253
269
  * }
@@ -293,23 +309,24 @@ export interface ArrayControlState<T> {
293
309
  */
294
310
  pending: boolean;
295
311
  /**
296
- * Массив ошибок валидации уровня массива.
297
- * Не включает ошибки отдельных элементов.
312
+ * Замечания массива: собственные (правила на ручке массива) и замечания всех его строк.
298
313
  *
299
314
  * @example
300
315
  * ```tsx
301
- * // Валидатор массива
302
- * validators.apply(phonesArray, {
303
- * validator: (phones) => phones.length >= 1,
304
- * message: 'At least one phone required'
305
- * });
316
+ * // Правило массива — в схеме валидации
317
+ * validate(model.$.phones, [minLength(1, { code: 'phones.minLength' })]);
306
318
  *
307
319
  * // В компоненте
308
- * const { errors } = useFormControl(phonesArray);
309
- * // errors содержит ошибку "At least one phone required" если массив пуст
320
+ * const { issues } = useFormControl(phonesArray);
321
+ * // для пустого массива issues содержит замечание с кодом 'phones.minLength'
310
322
  * ```
311
323
  */
312
- errors: ValidationError[];
324
+ issues: ValidationIssue[];
325
+ /**
326
+ * Собственные замечания массива — только правила на его ручке (`minLength` и т.п.), без
327
+ * замечаний строк. Их показывает ошибка уровня массива (`FieldArray.Error`).
328
+ */
329
+ ownIssues: ValidationIssue[];
313
330
  /**
314
331
  * Флаг валидности массива и всех его элементов.
315
332
  * `true` только когда массив и все вложенные элементы валидны.
@@ -327,7 +344,9 @@ export interface ArrayControlState<T> {
327
344
  touched: boolean;
328
345
  /**
329
346
  * Флаг изменения.
330
- * `true` когда значение массива отличается от начального.
347
+ * `true` после правки любого элемента или изменения состава через форму (`push`, `removeAt`,
348
+ * `move`, `setValue`…). Как у поля, запись в модель (загрузка данных) и операция, которая состав
349
+ * не поменяла, изменением не считаются; снимает `reset`.
331
350
  *
332
351
  * @example
333
352
  * ```tsx
@@ -344,7 +363,7 @@ export interface ArrayControlState<T> {
344
363
  dirty: boolean;
345
364
  /**
346
365
  * Флаг отключения массива.
347
- * `true` когда массив отключён (`ArrayNode.disable()`), в том числе через
366
+ * `true` когда массив отключён (`FormArray.disable()`), в том числе через
348
367
  * распространение disable от родительской группы.
349
368
  *
350
369
  * Используйте для отключения UI-действий добавления/удаления, когда массив