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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +90 -68
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +382 -217
  4. package/dist/create-model-BvcbNPgL.js +30 -0
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +42 -8
  9. package/dist/form/behaviors/context.d.ts +37 -4
  10. package/dist/form/behaviors/index.d.ts +6 -4
  11. package/dist/form/behaviors/internals.d.ts +9 -3
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +78 -0
  15. package/dist/form/behaviors/types.d.ts +25 -5
  16. package/dist/form/create-form.d.ts +57 -70
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +153 -0
  19. package/dist/form/form-config-base.d.ts +46 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +20 -20
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +153 -0
  37. package/dist/form/schema-subtree.d.ts +28 -0
  38. package/dist/form/signal-node-registry.d.ts +27 -22
  39. package/dist/form/submission.d.ts +93 -0
  40. package/dist/form/type-guards.d.ts +14 -73
  41. package/dist/form/types/contracts.d.ts +19 -92
  42. package/dist/form/types/field-config.d.ts +27 -0
  43. package/dist/form/types/index.d.ts +5 -57
  44. package/dist/form/types/nodes.d.ts +159 -0
  45. package/dist/form/types/schema-node.d.ts +147 -46
  46. package/dist/form/types/validation-schema.d.ts +23 -27
  47. package/dist/form/validation/context.d.ts +46 -6
  48. package/dist/form/validation/index.d.ts +9 -8
  49. package/dist/form/validation/operators.d.ts +71 -17
  50. package/dist/form/validation/run.d.ts +93 -18
  51. package/dist/form/validation/strategy.d.ts +41 -13
  52. package/dist/form/validation/types.d.ts +74 -19
  53. package/dist/form/validators/date-utils.d.ts +13 -1
  54. package/dist/form/validators/email.d.ts +8 -7
  55. package/dist/form/validators/file-type.d.ts +8 -7
  56. package/dist/form/validators/file-utils.d.ts +2 -1
  57. package/dist/form/validators/future-date.d.ts +8 -7
  58. package/dist/form/validators/index.d.ts +1 -1
  59. package/dist/form/validators/integer.d.ts +8 -7
  60. package/dist/form/validators/is-date.d.ts +11 -9
  61. package/dist/form/validators/is-number.d.ts +11 -9
  62. package/dist/form/validators/max-age.d.ts +13 -9
  63. package/dist/form/validators/max-date.d.ts +8 -7
  64. package/dist/form/validators/max-file-size.d.ts +8 -7
  65. package/dist/form/validators/max-files.d.ts +8 -7
  66. package/dist/form/validators/max-length.d.ts +14 -7
  67. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  68. package/dist/form/validators/max.d.ts +10 -8
  69. package/dist/form/validators/min-age.d.ts +12 -9
  70. package/dist/form/validators/min-date.d.ts +8 -7
  71. package/dist/form/validators/min-file-size.d.ts +8 -7
  72. package/dist/form/validators/min-files.d.ts +11 -9
  73. package/dist/form/validators/min-length.d.ts +14 -7
  74. package/dist/form/validators/min.d.ts +10 -8
  75. package/dist/form/validators/multiple-of.d.ts +10 -8
  76. package/dist/form/validators/non-negative.d.ts +8 -7
  77. package/dist/form/validators/non-zero.d.ts +8 -7
  78. package/dist/form/validators/past-date.d.ts +8 -7
  79. package/dist/form/validators/pattern.d.ts +9 -8
  80. package/dist/form/validators/phone.d.ts +8 -7
  81. package/dist/form/validators/required.d.ts +11 -9
  82. package/dist/form/validators/url.d.ts +20 -13
  83. package/dist/form/validators/validation-issue.d.ts +8 -0
  84. package/dist/i18n/descriptor.d.ts +1 -1
  85. package/dist/i18n/en.json.d.ts +5 -1
  86. package/dist/i18n/locale-store.d.ts +7 -0
  87. package/dist/i18n/message-format.d.ts +24 -4
  88. package/dist/i18n/ru.json.d.ts +5 -1
  89. package/dist/i18n/translator.d.ts +11 -0
  90. package/dist/i18n/validation-message.d.ts +42 -7
  91. package/dist/i18n.d.ts +7 -12
  92. package/dist/i18n.js +66 -283
  93. package/dist/index.d.ts +16 -7
  94. package/dist/index.js +958 -2798
  95. package/dist/loader-BWkigpVe.js +52 -0
  96. package/dist/locale/en.js +1 -1
  97. package/dist/locale/en.json +4 -1
  98. package/dist/locale/ru.js +4 -1
  99. package/dist/locale/ru.json +4 -1
  100. package/dist/locale-CG54AU2Q.js +219 -0
  101. package/dist/locale.js +1 -1
  102. package/dist/model/behaviors-value.d.ts +24 -6
  103. package/dist/model/create-model.d.ts +2 -2
  104. package/dist/model/derived-registry.d.ts +1 -1
  105. package/dist/model/index.d.ts +16 -5
  106. package/dist/model/model-nodes.d.ts +138 -8
  107. package/dist/model/model-signals-proxy.d.ts +11 -5
  108. package/dist/model/model-value-proxy.d.ts +84 -3
  109. package/dist/model/safe-effect.d.ts +33 -1
  110. package/dist/model/types.d.ts +70 -17
  111. package/dist/model.js +21 -17
  112. package/dist/node-kind-C-r6ZMdo.js +15 -0
  113. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  114. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  115. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  116. package/dist/platforms/react/hooks/types.d.ts +57 -38
  117. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  118. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  119. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  120. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  121. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  122. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  123. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  124. package/dist/platforms/react/i18n/context.d.ts +6 -1
  125. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  126. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  127. package/dist/platforms/react/index.d.ts +7 -4
  128. package/dist/react.d.ts +22 -0
  129. package/dist/react.js +505 -0
  130. package/dist/runtime-token.d.ts +1 -2
  131. package/dist/safe-effect-BbtX3KuN.js +643 -0
  132. package/dist/schema-controller-CpiO2JoK.js +143 -0
  133. package/dist/signals.d.ts +15 -9
  134. package/dist/signals.js +11 -6
  135. package/dist/strategy-CoFSemuP.js +375 -0
  136. package/dist/type-guards-D-N_5Ddh.js +19 -0
  137. package/dist/validation-issue-B9mjI8jz.js +7 -0
  138. package/dist/validation-message-Ced9MGxs.js +194 -0
  139. package/dist/validation.js +9 -8
  140. package/dist/validators/email.js +5 -5
  141. package/dist/validators/future-date.js +8 -8
  142. package/dist/validators/integer.js +3 -3
  143. package/dist/validators/is-date.js +4 -4
  144. package/dist/validators/is-number.js +4 -4
  145. package/dist/validators/max-age.js +8 -9
  146. package/dist/validators/max-date.js +8 -8
  147. package/dist/validators/max-length.js +7 -7
  148. package/dist/validators/max.js +4 -4
  149. package/dist/validators/min-age.js +10 -8
  150. package/dist/validators/min-date.js +3 -3
  151. package/dist/validators/min-length.js +5 -5
  152. package/dist/validators/min.js +4 -4
  153. package/dist/validators/multiple-of.js +11 -7
  154. package/dist/validators/non-negative.js +3 -3
  155. package/dist/validators/non-zero.js +3 -3
  156. package/dist/validators/past-date.js +8 -8
  157. package/dist/validators/pattern.js +5 -5
  158. package/dist/validators/phone.js +11 -9
  159. package/dist/validators/required.js +2 -2
  160. package/dist/validators/url.js +48 -12
  161. package/dist/validators.js +17 -17
  162. package/docs/llms/01-api-reference.md +60 -34
  163. package/docs/llms/02-quick-start.md +111 -80
  164. package/docs/llms/03-api-signatures.md +368 -153
  165. package/docs/llms/04-common-patterns.md +58 -56
  166. package/docs/llms/05-common-mistakes.md +69 -44
  167. package/docs/llms/06-troubleshooting.md +14 -9
  168. package/docs/llms/07-complete-import.md +74 -43
  169. package/docs/llms/08-form-types.md +11 -4
  170. package/docs/llms/09-formschema.md +131 -83
  171. package/docs/llms/10-arrays.md +119 -69
  172. package/docs/llms/11-async-watchfield.md +2 -2
  173. package/docs/llms/13-multi-step.md +134 -63
  174. package/docs/llms/14-extended-mistakes.md +20 -17
  175. package/docs/llms/15-project-structure.md +221 -74
  176. package/docs/llms/16-ui-components.md +55 -42
  177. package/docs/llms/17-nonexistent-api.md +199 -32
  178. package/docs/llms/18-conditional-fields.md +66 -32
  179. package/docs/llms/19-reading-values.md +15 -3
  180. package/docs/llms/20-compute-vs-watch.md +16 -10
  181. package/docs/llms/21-array-operations.md +76 -26
  182. package/docs/llms/22-cycle-detection.md +1 -1
  183. package/docs/llms/23-copy-from.md +2 -2
  184. package/docs/llms/24-sync-fields.md +6 -5
  185. package/docs/llms/25-reset-when.md +14 -7
  186. package/docs/llms/26-transform-value.md +4 -4
  187. package/docs/llms/27-revalidate-when.md +16 -15
  188. package/docs/llms/28-submit-and-reset.md +276 -128
  189. package/docs/llms/29-async-preload.md +40 -30
  190. package/docs/llms/30-type-safety-recipes.md +105 -51
  191. package/docs/llms/31-async-validator-debounce.md +96 -53
  192. package/docs/llms/33-validation-strategy.md +178 -66
  193. package/docs/llms/34-i18n.md +69 -43
  194. package/docs/llms/35-unified-contract.md +307 -0
  195. package/docs/llms/36-issue-messages.md +284 -0
  196. package/docs/llms/37-compiled-schema.md +332 -0
  197. package/docs/llms/38-form-flow.md +397 -0
  198. package/llms-index.json +1 -1
  199. package/llms.txt +6968 -3278
  200. package/package.json +23 -4
  201. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  202. package/dist/create-model-B8U-COv-.js +0 -103
  203. package/dist/date-utils-dTT_x_Be.js +0 -31
  204. package/dist/form/create-core-form.d.ts +0 -70
  205. package/dist/form/factories/index.d.ts +0 -6
  206. package/dist/form/factories/node-factory.d.ts +0 -189
  207. package/dist/form/form-submitter.d.ts +0 -121
  208. package/dist/form/nodes/array-node.d.ts +0 -276
  209. package/dist/form/nodes/field-node.d.ts +0 -297
  210. package/dist/form/nodes/form-node.d.ts +0 -326
  211. package/dist/form/nodes/group-node.d.ts +0 -284
  212. package/dist/form/nodes/model-array-node.d.ts +0 -121
  213. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  214. package/dist/form/status-machine.d.ts +0 -115
  215. package/dist/form/types/deep-schema.d.ts +0 -107
  216. package/dist/form/types/form-proxy.d.ts +0 -141
  217. package/dist/form/unique-id.d.ts +0 -45
  218. package/dist/form/validation/config.d.ts +0 -73
  219. package/dist/form/validation/error-handler.d.ts +0 -166
  220. package/dist/form/validators/validation-error.d.ts +0 -8
  221. package/dist/index-BrNkQj1y.js +0 -105
  222. package/dist/loader-Dv_Hl7JC.js +0 -216
  223. package/dist/model-signals-proxy-2vqS243J.js +0 -250
  224. package/dist/node-DGEFMTQu.js +0 -18
  225. package/dist/strategy-BXS_gBfz.js +0 -170
  226. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -0,0 +1,30 @@
1
+ import { M as f, j as c, k as r, l } from "./safe-effect-BbtX3KuN.js";
2
+ function n(o, e, a) {
3
+ if (o.kind === "leaf") {
4
+ e(o.signal);
5
+ return;
6
+ }
7
+ if (o.kind === "group") {
8
+ for (const t of o.children.values()) n(t, e, a);
9
+ return;
10
+ }
11
+ a && e(l(o));
12
+ for (const t of o.items.value) n(t, e, a);
13
+ }
14
+ function u(o, e) {
15
+ const a = r.get(o);
16
+ a && n(a, e, !1);
17
+ }
18
+ function i(o, e) {
19
+ const a = r.get(o);
20
+ a && n(a, e, !0);
21
+ }
22
+ function g(o) {
23
+ const e = new f(o, "");
24
+ return c(e);
25
+ }
26
+ export {
27
+ i as a,
28
+ g as c,
29
+ u as e
30
+ };
@@ -0,0 +1,43 @@
1
+ const a = /^(\d{4})-(\d{2})-(\d{2})$/;
2
+ function g(t) {
3
+ if (t == null || t === "")
4
+ return null;
5
+ if (t instanceof Date)
6
+ return isNaN(t.getTime()) ? null : t;
7
+ if (typeof t == "string") {
8
+ const e = a.exec(t);
9
+ return e ? l(Number(e[1]), Number(e[2]) - 1, Number(e[3])) : null;
10
+ }
11
+ return null;
12
+ }
13
+ function l(t, e, r) {
14
+ const n = /* @__PURE__ */ new Date(0);
15
+ return n.setFullYear(t, e, r), n.setHours(0, 0, 0, 0), n.getFullYear() === t && n.getMonth() === e && n.getDate() === r ? n : null;
16
+ }
17
+ function i() {
18
+ const t = /* @__PURE__ */ new Date();
19
+ return t.setHours(0, 0, 0, 0), t;
20
+ }
21
+ function u(t) {
22
+ const e = new Date(t);
23
+ return e.setHours(0, 0, 0, 0), e;
24
+ }
25
+ const c = (t) => t % 4 === 0 && t % 100 !== 0 || t % 400 === 0;
26
+ function f(t) {
27
+ return u(t) > i();
28
+ }
29
+ function D(t) {
30
+ const e = i(), r = u(t);
31
+ let n = r.getDate();
32
+ r.getMonth() === 1 && n === 29 && !c(e.getFullYear()) && (n = 28);
33
+ let o = e.getFullYear() - r.getFullYear();
34
+ const s = e.getMonth() - r.getMonth();
35
+ return (s < 0 || s === 0 && e.getDate() < n) && (o -= 1), o;
36
+ }
37
+ export {
38
+ D as c,
39
+ i as g,
40
+ f as i,
41
+ u as n,
42
+ g as p
43
+ };
@@ -1,4 +1,4 @@
1
- const a = {
1
+ const e = {
2
2
  "format.fileSize.b": "{value} B",
3
3
  "format.fileSize.kb": "{value} KB",
4
4
  "format.fileSize.mb": "{value} MB",
@@ -14,6 +14,8 @@ const a = {
14
14
  "validation.max": "Must be at most {max}",
15
15
  "validation.minLength": "Enter at least {minLength, plural, one{# character} other{# characters}}",
16
16
  "validation.maxLength": "Enter no more than {maxLength, plural, one{# character} other{# characters}}",
17
+ "validation.minItems": "At least {minLength, plural, one{# item} other{# items}}",
18
+ "validation.maxItems": "No more than {maxLength, plural, one{# item} other{# items}}",
17
19
  "validation.isNumber": "Enter a number",
18
20
  "validation.integer": "Enter a whole number",
19
21
  "validation.multipleOf": "Must be a multiple of {multipleOf}",
@@ -34,8 +36,9 @@ const a = {
34
36
  "validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
35
37
  "validation.fileExists": "File {fileName} has already been added",
36
38
  "validation.uploadAborted": "Upload cancelled",
37
- "validation.uploadFailed": "Failed to upload {fileName}"
39
+ "validation.uploadFailed": "Failed to upload {fileName}",
40
+ "validation.ruleFailed": "Could not check this field. Try again"
38
41
  };
39
42
  export {
40
- a as m
43
+ e as m
41
44
  };
@@ -1,71 +1,29 @@
1
- import { ReadonlySignal, Signal } from '@preact/signals-core';
2
- import { FieldStatus, ValidationError } from './types/index';
3
- import { FormNode } from './nodes/form-node';
4
- /**
5
- * Функция получения дочерних узлов
6
- * Возвращает массив FormNode для агрегации состояния
7
- */
8
- type GetChildrenFn<T> = () => FormNode<T>[];
9
- /**
10
- * Результат создания агрегированных сигналов
11
- */
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { ValidationIssue } from './types/contracts';
3
+ import { FormNode } from './types/nodes';
12
4
  export interface AggregateSignals {
13
- /** Все дочерние узлы валидны и нет собственных ошибок */
5
+ /**
6
+ * Контейнер включён, блокирующих своих замечаний нет, никто не ждёт проверки, каждый включённый
7
+ * ребёнок валиден.
8
+ */
14
9
  valid: ReadonlySignal<boolean>;
15
- /** Есть ошибки (инверсия valid) */
10
+ /** Никто не ждёт проверки, и есть блокирующее своё замечание или включённый невалидный ребёнок. */
16
11
  invalid: ReadonlySignal<boolean>;
17
- /** Хотя бы один дочерний узел в состоянии pending */
12
+ /** Контейнер или хотя бы один ребёнок ждёт проверки. */
18
13
  pending: ReadonlySignal<boolean>;
19
- /** Хотя бы один дочерний узел touched */
20
14
  touched: ReadonlySignal<boolean>;
21
- /** Хотя бы один дочерний узел dirty */
22
15
  dirty: ReadonlySignal<boolean>;
23
- /** Собственные ошибки + ошибки всех дочерних узлов */
24
- errors: ReadonlySignal<ValidationError[]>;
25
- /** Статус: disabled > pending > invalid > valid */
26
- status: ReadonlySignal<FieldStatus>;
16
+ /** Свои замечания и замечания всех детей. */
17
+ issues: ReadonlySignal<readonly ValidationIssue[]>;
27
18
  }
28
- /**
29
- * Опции для создания агрегированных сигналов
30
- */
31
- export interface AggregateSignalsOptions<T> {
32
- /** Функция получения дочерних узлов */
33
- getChildren: GetChildrenFn<T>;
34
- /** Signal с собственными ошибками контейнера (form-level или array-level) */
35
- ownErrors: Signal<ValidationError[]>;
36
- /** Опциональный signal disabled состояния (для GroupNode) */
37
- disabled?: Signal<boolean>;
19
+ export interface AggregateSignalsOptions {
20
+ /** Дети контейнера; чтение внутри `computed` подписывает на их состав. */
21
+ getChildren: () => readonly FormNode<unknown>[];
22
+ /** Замечания самого контейнера. */
23
+ ownIssues: ReadonlySignal<readonly ValidationIssue[]>;
24
+ /** Ожидание async-правила на ручке самого контейнера (`validateAsync(model.$.items, …)`). */
25
+ ownPending?: ReadonlySignal<boolean>;
26
+ /** Выключен ли сам контейнер: выключенный — ни `valid`, ни `invalid`, ни `pending`, как поле. */
27
+ disabled?: ReadonlySignal<boolean>;
38
28
  }
39
- /**
40
- * Создать агрегированные computed signals для контейнерного узла
41
- *
42
- * Используется в GroupNode и ArrayNode для унификации логики
43
- * вычисления состояния на основе дочерних узлов.
44
- *
45
- * @param options - Опции конфигурации
46
- * @returns Объект с computed signals
47
- *
48
- * @example GroupNode
49
- * ```typescript
50
- * const signals = createAggregateSignals({
51
- * getChildren: () => Array.from(this._fields.values()),
52
- * ownErrors: this._formErrors,
53
- * disabled: this._disabled,
54
- * });
55
- * this.valid = signals.valid;
56
- * this.invalid = signals.invalid;
57
- * // ...
58
- * ```
59
- *
60
- * @example ArrayNode
61
- * ```typescript
62
- * const signals = createAggregateSignals({
63
- * getChildren: () => this.items.value,
64
- * ownErrors: this._arrayErrors,
65
- * });
66
- * this.valid = signals.valid;
67
- * // ...
68
- * ```
69
- */
70
- export declare function createAggregateSignals<T>(options: AggregateSignalsOptions<T>): AggregateSignals;
71
- export {};
29
+ export declare function createAggregateSignals(options: AggregateSignalsOptions): AggregateSignals;
@@ -1,6 +1,22 @@
1
1
  import { Signal } from '@preact/signals-core';
2
- import { FormModel } from '../../index';
2
+ import { FormModel, ModelArray } from '../../model/types';
3
3
  import { FormBehavior } from './types';
4
+ /**
5
+ * Ручка массива под-форм `model.$.<массив>`. Тип строки выводится из её `peek()` — так же, как у
6
+ * `applyEach` валидации. Раньше цель была `object`: тип строки не выводился (`unknown`), а чужая
7
+ * схема строки и ручка не того вида проходили компиляцию.
8
+ */
9
+ interface ArrayHandle<TItem> {
10
+ peek(): readonly TItem[] | null | undefined;
11
+ }
12
+ /** Цель операторов массива: ручка `model.$.<массив>` либо его value-фасад `model.<массив>`. */
13
+ type ArrayTarget<TItem> = ArrayHandle<TItem> | ModelArray<TItem>;
14
+ /** Ручка группы `model.$.<группа>`: тип значения — из её `peek()`. */
15
+ interface GroupHandle<V> {
16
+ peek(): V;
17
+ }
18
+ /** Цель `apply`: ручка `model.$.<группа>` либо её value-фасад `model.<группа>`. */
19
+ type GroupTarget<V> = GroupHandle<V> | FormModel<NonNullable<V>>;
4
20
  /**
5
21
  * Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
6
22
  * Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
@@ -8,26 +24,34 @@ import { FormBehavior } from './types';
8
24
  * Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
9
25
  * - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
10
26
  * - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
11
- * МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
27
+ * МАТЕРИАЛИЗОВАН в форме (узел `{ model: model.$.<массив>, item }` в схеме) — тогда `form` строки = та же нода, что
12
28
  * рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
13
29
  * к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
14
30
  *
31
+ * Работает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки
32
+ * можно снова вызвать `applyEach`.
33
+ *
15
34
  * @example
16
35
  * applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
17
36
  * compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
18
37
  * enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
38
+ * applyEach(row.$.phones, phoneBehavior); // массив внутри строки
19
39
  * }));
20
40
  */
21
- export declare function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void;
41
+ export declare function applyEach<TItem extends object>(array: ArrayTarget<TItem>, itemSchema: FormBehavior<NoInfer<TItem>>): void;
22
42
  /**
23
43
  * Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
24
44
  * Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
25
45
  * новые строки с флагом false исключения не запускают.
26
46
  *
47
+ * Начальные данные и добавленные строки флаг не МЕНЯЮТ — он приходит сразу `true`. Такая строка
48
+ * прежний выбор не снимает: флаг сбрасывается у неё. Из нескольких пришедших с `true` сразу
49
+ * (начальные данные) остаётся первая по порядку строк.
50
+ *
27
51
  * @example
28
52
  * exclusiveFlag(model.$.contacts, (row) => row.$.primary);
29
53
  */
30
- export declare function exclusiveFlag<TItem>(array: object, getFlag: (row: FormModel<TItem>) => Signal<boolean>): void;
54
+ export declare function exclusiveFlag<TItem extends object>(array: ArrayTarget<TItem>, getFlag: (row: FormModel<NoInfer<TItem>>) => Signal<boolean>): void;
31
55
  /**
32
56
  * Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
33
57
  * `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
@@ -42,9 +66,19 @@ export declare function exclusiveFlag<TItem>(array: object, getFlag: (row: FormM
42
66
  * return [{ index: n - 1, patch: { percent: 100 - others } }];
43
67
  * });
44
68
  */
45
- export declare function aggregateInto<TItem>(array: object, derive: (rows: TItem[]) => Array<{
69
+ export declare function aggregateInto<TItem extends object>(array: ArrayTarget<TItem>, derive: (rows: NoInfer<TItem>[]) => Array<{
46
70
  index: number;
47
- patch: Partial<TItem>;
71
+ patch: Partial<NoInfer<TItem>>;
48
72
  }>): void;
49
- /** Применить под-схему к одному или нескольким полям-группам (переиспользование). */
50
- export declare function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void;
73
+ /**
74
+ * Применить под-схему к одному или нескольким полям-группам (переиспользование).
75
+ *
76
+ * Под-схема получает scope группы: `model` — настоящая под-модель (`model.$.field`, чтение и
77
+ * запись значений, `get`/`set`/`patch`), `form` — нода группы. Цель — ручка группы
78
+ * (`model.$.address`); работает и в корневой схеме, и в схеме строки массива.
79
+ *
80
+ * @example
81
+ * apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
82
+ */
83
+ export declare function apply<V extends object | null | undefined>(targets: GroupTarget<V> | GroupTarget<V>[], subSchema: FormBehavior<NoInfer<NonNullable<V>>>): void;
84
+ export {};
@@ -1,23 +1,56 @@
1
- import { BehaviorCleanup } from '../../index';
1
+ import { BehaviorCleanup } from '../../model/behaviors-value';
2
+ import { SchemaController } from '../schema-controller';
2
3
  import { BehaviorScope, FormBehavior } from './types';
4
+ import { FormFlow } from '../flow/flow';
3
5
  /** Зарегистрировать произвольную отписку в активной схеме. */
4
6
  export declare function onDispose(cleanup: BehaviorCleanup): void;
5
- /** Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов). */
7
+ /**
8
+ * Оператор вызван внутри схемы поведения? Иначе — понятная ошибка с именем оператора ДО любых
9
+ * побочных эффектов: effect, созданный до проверки, остался бы жить, а признак derived — висеть.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare function assertInsideSchema(op: string): void;
14
+ /**
15
+ * Зарегистрировать отписку, если оператор вызван внутри схемы поведения; вне схемы — ничего.
16
+ * Для операторов, которые работают и сами по себе (правила узлов схемы).
17
+ *
18
+ * @internal
19
+ */
20
+ export declare function onDisposeIfActive(cleanup: BehaviorCleanup): void;
21
+ /** Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов). */
6
22
  export declare function getScope<T>(): BehaviorScope<T>;
23
+ /**
24
+ * Схема-контроллер сборки, в которой идёт активная схема: под-схемы (`apply`, `applyEach`)
25
+ * получают через него область своей под-модели.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function getController(op?: string): SchemaController;
30
+ /**
31
+ * Поток шагов текущего прогона схемы — его получают поведения подформ и строк массива.
32
+ *
33
+ * @internal
34
+ */
35
+ export declare function getFlow(op?: string): FormFlow | undefined;
7
36
  /**
8
37
  * Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
9
38
  *
10
39
  * @example
11
40
  * ```ts
12
- * export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
41
+ * export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
13
42
  * compute(model.$.total, () => model.price * model.qty);
14
43
  * enableWhen([model.$.city], () => Boolean(model.country));
15
44
  * onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
45
+ * hideWhen(schema.node('delivery'), () => model.country === '');
16
46
  * });
17
47
  * ```
18
48
  */
19
49
  export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
20
50
  /** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
21
51
  export declare function effect(fn: () => void | (() => void)): void;
22
- /** Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». */
52
+ /**
53
+ * Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». Внутри схемы
54
+ * поведения (в `setup`, в её effect'ах и отложенных заданиях) запись отменяется снятием схемы.
55
+ */
23
56
  export declare function defer(fn: () => void): void;
@@ -2,16 +2,16 @@
2
2
  * Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
3
3
  *
4
4
  * Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
5
- * `defineFormBehavior(({ model, form }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
5
+ * `defineFormBehavior(({ model, form, schema }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
6
6
  * автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
7
7
  * владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
8
8
  * ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
9
9
  *
10
10
  * Граница ответственности: контракт НЕ управляет валидацией — это отдельный слой
11
- * (`defineValidationSchema` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
12
- * владельцем `errors`. Это сделано намеренно. Для редких крайних случаев
11
+ * (`defineValidation` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
12
+ * владельцем `issues`. Это сделано намеренно. Для редких крайних случаев
13
13
  * behavior-driven валидации (cross-field, async-uniqueness) можно написать собственный оператор поверх
14
- * `effect`/`onChange` + `node.setErrors(...)` — он будет неотличим от встроенного; примеры см. в
14
+ * `effect`/`onChange` + `node.setIssues(...)` — он будет неотличим от встроенного; примеры см. в
15
15
  * tests/behaviors/web-scenarios (W1/W3). В общем случае правила валидности держите в слое валидации.
16
16
  *
17
17
  * Модуль разложен по зонам ответственности: `./types` — контракт схемы, `./context` — ambient-сток
@@ -26,3 +26,5 @@ export type { Signal, ReadonlySignal, BehaviorCleanup } from './types';
26
26
  export { onDispose, getScope, defineFormBehavior, effect, defer } from './context';
27
27
  export { compute, computeFrom, copyFrom, onChange, enableWhen, disableWhen, transformValue, resetWhen, syncFields, revalidateWhen, } from './operators';
28
28
  export { applyEach, exclusiveFlag, aggregateInto, apply } from './collections';
29
+ export { hideWhen, onComponentEvent, renderEffect, onInit, onMount, onUnmount, } from './schema-operators';
30
+ export type { SchemaScope, SchemaNodeControl } from '../schema-controller';
@@ -24,12 +24,18 @@ export declare const isLeafSignal: (v: unknown) => v is Signal<unknown>;
24
24
  export declare const isValueHandle: (v: unknown) => v is Signal<unknown>;
25
25
  export declare const asArray: <X>(v: X | X[]) => X[];
26
26
  export declare function readGroup(g: GroupSignals): Record<string, unknown>;
27
- export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>): void;
28
27
  /**
29
- * Guard от расходящихся циклов пересчёта (F7). Расходящийся взаимный compute/computeFrom (без
28
+ * Запись значения в группу — как полная установка `model.<группа>.set(...)`: поля цели, которых в
29
+ * значении нет, остаются прежними, а не стираются. Их пути (относительно группы) складываются в
30
+ * `missing` — по ним вызывающий предупреждает в dev. Значение не объект — группа не тронута.
31
+ *
32
+ * @param at - Путь текущей группы относительно корня записи (для `missing`).
33
+ */
34
+ export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>, missing?: string[], at?: string): void;
35
+ /**
36
+ * Guard от расходящихся циклов пересчёта. Расходящийся взаимный compute/computeFrom (без
30
37
  * стабилизации) preact обрывает невнятным «Cycle detected» — перехватываем и заменяем понятной
31
38
  * ошибкой с именем поля и подсказкой. Сходящиеся compute (упираются в peek-guard) и массовые
32
39
  * синхронные мутации цикла не порождают → не затрагиваются.
33
40
  */
34
41
  export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
35
- export declare function getByPath(root: unknown, path: string): any;
@@ -1,11 +1,34 @@
1
1
  import { ReadonlySignal } from '@preact/signals-core';
2
2
  import { BehaviorCleanup } from '../../model/behaviors-value';
3
+ /** Нода формы, чьей доступностью управляет оператор: поле, группа или массив под-форм. */
4
+ export interface AvailabilityTarget {
5
+ enable(): void;
6
+ disable(): void;
7
+ }
8
+ /**
9
+ * Отложенное применение доступности ноды по условию.
10
+ *
11
+ * Решение принимается в момент записи, а новое задание отменяет ожидающее (побеждает последнее):
12
+ * иначе задание «выключить и стереть» первого прогона выполнилось бы после загрузки данных в том же
13
+ * тике и стёрло их, хотя поле к этому моменту уже снова доступно. Снятие отменяет ожидающее
14
+ * задание — запись после dispose не случится.
15
+ *
16
+ * @internal
17
+ */
18
+ export declare function availabilityWrites(condition: () => boolean, options?: {
19
+ resetOnDisable?: boolean;
20
+ }): {
21
+ schedule(node: AvailabilityTarget): void;
22
+ cancel(): void;
23
+ };
3
24
  /**
4
25
  * Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
5
26
  * сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
6
27
  * `resetOnDisable`). `condition` реактивен (читает свои сигналы модели).
7
28
  *
8
- * Запись состояния отложена через `runOutsideEffect` (микротаск) для защиты от «Cycle detected».
29
+ * Запись состояния отложена на микротаск для защиты от «Cycle detected». Условие читается в момент
30
+ * записи, и побеждает последнее решение: загрузка данных в том же тике, что и сборка, не стирается
31
+ * заданием первого прогона. Снятие отменяет ещё не выполненную запись.
9
32
  * ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
10
33
  * элемент массива, который строится per-item).
11
34
  *
@@ -14,7 +37,7 @@ import { BehaviorCleanup } from '../../model/behaviors-value';
14
37
  * работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
15
38
  * в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
16
39
  * Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
17
- * `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`
40
+ * `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()` и отправка
18
41
  * включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
19
42
  * которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
20
43
  *
@@ -1,14 +1,21 @@
1
1
  import { Signal, ReadonlySignal } from '@preact/signals-core';
2
+ import { ResetWhenOptionsArg } from '../../model/behaviors-value';
2
3
  import { ChangeContext } from './types';
3
4
  /** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
4
- export declare function compute<R>(target: Signal<R>, read: () => R, options?: {
5
+ export declare function compute<R>(target: Signal<R>, read: () => NoInfer<R>, options?: {
5
6
  when?: () => boolean;
6
7
  }): void;
7
8
  /** Вычисляемое поле с явным списком зависимостей (escape hatch). */
8
- export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => R, options?: {
9
+ export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => NoInfer<R>, options?: {
9
10
  when?: (...values: any[]) => boolean;
10
11
  }): void;
11
- /** Копирование `source → target` — скаляр или группа (объект целиком). */
12
+ /**
13
+ * Копирование `source → target` — скаляр или группа (объект целиком).
14
+ *
15
+ * Группы разной формы копируются как `set` группы модели: поля цели, которых нет в значении,
16
+ * остаются прежними; в dev об этом одно предупреждение на оператор. Значение цели целиком
17
+ * собирает `transform`.
18
+ */
12
19
  export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
13
20
  when?: () => boolean;
14
21
  transform?: (value: T) => T;
@@ -21,7 +28,7 @@ export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target:
21
28
  *
22
29
  * Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
23
30
  * предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
24
- * `signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
31
+ * `signal.aborted` перед применением результата — это убирает гонки устаревших ответов.
25
32
  */
26
33
  export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
27
34
  immediate?: boolean;
@@ -38,12 +45,13 @@ export declare function disableWhen(target: EnableTarget | EnableTarget[], condi
38
45
  }): void;
39
46
  /** Трансформация значения поля (идемпотентная). */
40
47
  export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;
41
- /** Сброс значения по условию. */
42
- export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: {
43
- resetValue?: T;
44
- }): void;
48
+ /**
49
+ * Сброс значения по условию. Без `resetValue` поле сбрасывается в `null` — поэтому полю, в тип
50
+ * которого `null` не входит, `resetValue` обязателен (см. {@link ResetWhenOptionsArg}).
51
+ */
52
+ export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, ...options: ResetWhenOptionsArg<T>): void;
45
53
  /** Двусторонняя синхронизация двух полей. */
46
- export declare function syncFields<T>(a: Signal<T>, b: Signal<T>, options?: {
54
+ export declare function syncFields<T>(a: Signal<T>, b: Signal<NoInfer<T>>, options?: {
47
55
  transform?: (value: T) => T;
48
56
  }): void;
49
57
  /** Ревалидация при изменении зависимостей. */
@@ -0,0 +1,78 @@
1
+ import { SchemaNodeControl, SchemaScope } from '../schema-controller';
2
+ /**
3
+ * Скрывать узел, пока условие истинно.
4
+ *
5
+ * Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
6
+ * Принудительное `schema.node(selector).setHidden(...)` главнее условия.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * hideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');
11
+ * ```
12
+ */
13
+ export declare function hideWhen(node: SchemaNodeControl, condition: () => boolean): void;
14
+ /**
15
+ * Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
16
+ *
17
+ * Обработчик получает те же аргументы, что и исходный проп компонента. У контейнера он перекрывает
18
+ * одноимённый проп из `componentProps`. У поля и массива событие уже занято привязкой (запись
19
+ * значения в форму, кнопки массива) — обработчик вызывается после неё, а не вместо: ввод не ломается.
20
+ * У подформы своего компонента нет — правило не действует, в dev рендерер предупреждает.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
25
+ * await submitApplication(model.get());
26
+ * });
27
+ * ```
28
+ */
29
+ export declare function onComponentEvent(node: SchemaNodeControl, event: string, handler: (...args: any[]) => any): void;
30
+ /**
31
+ * Реактивный эффект, живущий, пока схема области смонтирована рендерером.
32
+ *
33
+ * В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
34
+ * (`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const panel = schema.node('summary').getRef<HTMLElement>();
39
+ * renderEffect(schema, () => {
40
+ * if (model.loanType === 'mortgage') panel.current?.scrollIntoView();
41
+ * });
42
+ * ```
43
+ */
44
+ export declare function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void;
45
+ /**
46
+ * Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
47
+ * хук, способный повлиять на первый рендер, — внутри можно править пропсы через
48
+ * `schema.node(selector).patchProps({ … })`.
49
+ *
50
+ * К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
51
+ * {@link onMount} / {@link onUnmount}.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * onInit(schema.node('summary'), () => {
56
+ * schema.node('summary').patchProps({ currency: detectCurrency() });
57
+ * });
58
+ * ```
59
+ */
60
+ export declare function onInit(_node: SchemaNodeControl, fn: () => void): void;
61
+ /**
62
+ * Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * onMount(schema.node('data-boundary'), () => void loadApplication());
67
+ * ```
68
+ */
69
+ export declare function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void;
70
+ /**
71
+ * Хук перед размонтированием узла.
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
76
+ * ```
77
+ */
78
+ export declare function onUnmount(node: SchemaNodeControl, fn: () => void): void;
@@ -1,14 +1,34 @@
1
1
  import { Signal, ReadonlySignal } from '@preact/signals-core';
2
- import { BehaviorCleanup, FormModel, FormProxy } from '../../index';
3
- /** Контекст схемы поведения: модель (значения/сигналы) + форма (ноды). */
2
+ import { BehaviorCleanup } from '../../model/behaviors-value';
3
+ import { FormModel } from '../../model/types';
4
+ import { FormGroup } from '../types/index';
5
+ import { SchemaController, SchemaScope } from '../schema-controller';
6
+ import { FormFlow } from '../flow/flow';
7
+ /**
8
+ * Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).
9
+ *
10
+ * `schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении
11
+ * подформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов
12
+ * (`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.
13
+ */
4
14
  export type BehaviorScope<T> = {
5
15
  model: FormModel<T>;
6
- form: FormProxy<T>;
16
+ form: FormGroup<T>;
17
+ schema: SchemaScope;
18
+ /**
19
+ * Поток шагов сборки (`createForm({ flow })`); без потока — `undefined`. Один на сборку: его
20
+ * же получают поведения подформ и строк массива.
21
+ */
22
+ flow: FormFlow | undefined;
7
23
  };
8
24
  /** Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`. */
9
25
  export interface FormBehavior<T> {
10
- /** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
11
- __run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
26
+ /**
27
+ * @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.
28
+ * `controller` — схема-контроллер сборки; без него поведение получает собственный, который
29
+ * никто не читает (модель без рендерера). `flow` — поток шагов сборки.
30
+ */
31
+ __run(model: FormModel<T>, form: FormGroup<T>, controller?: SchemaController, flow?: FormFlow): BehaviorCleanup;
12
32
  }
13
33
  export type { Signal, ReadonlySignal, BehaviorCleanup };
14
34
  /** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */