@reformer/core 10.2.0 → 11.0.0-beta.10

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 (285) hide show
  1. package/README.md +90 -68
  2. package/dist/behaviors-value-D05z3my6.js +109 -0
  3. package/dist/behaviors.d.ts +1 -1
  4. package/dist/behaviors.js +376 -211
  5. package/dist/create-model-DPSG3I0O.js +30 -0
  6. package/dist/date-utils-90-qZbse.js +43 -0
  7. package/dist/en-HP8XEych.js +44 -0
  8. package/dist/form/aggregate-signals.d.ts +21 -63
  9. package/dist/form/behaviors/collections.d.ts +84 -0
  10. package/dist/form/behaviors/context.d.ts +56 -0
  11. package/dist/form/behaviors/index.d.ts +30 -0
  12. package/dist/form/behaviors/internals.d.ts +41 -0
  13. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
  14. package/dist/form/behaviors/operators.d.ts +59 -0
  15. package/dist/form/behaviors/schema-operators.d.ts +83 -0
  16. package/dist/form/behaviors/types.d.ts +37 -0
  17. package/dist/form/create-form.d.ts +59 -70
  18. package/dist/form/flow/flow.d.ts +87 -0
  19. package/dist/form/form-bundle.d.ts +153 -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 +23 -27
  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 +159 -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 +21 -91
  42. package/dist/form/types/field-config.d.ts +27 -0
  43. package/dist/form/types/index.d.ts +5 -91
  44. package/dist/form/types/nodes.d.ts +159 -0
  45. package/dist/form/types/schema-node.d.ts +148 -50
  46. package/dist/form/types/validation-schema.d.ts +24 -41
  47. package/dist/form/validation/context.d.ts +67 -0
  48. package/dist/form/validation/index.d.ts +24 -28
  49. package/dist/form/validation/operators.d.ts +101 -0
  50. package/dist/form/validation/run.d.ts +120 -0
  51. package/dist/form/validation/strategy.d.ts +89 -0
  52. package/dist/form/validation/types.d.ts +83 -0
  53. package/dist/form/validators/date-utils.d.ts +42 -0
  54. package/dist/form/validators/email.d.ts +23 -0
  55. package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
  56. package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
  57. package/dist/form/validators/future-date.d.ts +23 -0
  58. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  59. package/dist/form/validators/integer.d.ts +22 -0
  60. package/dist/form/validators/is-date.d.ts +24 -0
  61. package/dist/form/validators/is-number.d.ts +25 -0
  62. package/dist/form/validators/max-age.d.ts +28 -0
  63. package/dist/form/validators/max-date.d.ts +25 -0
  64. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
  65. package/dist/form/validators/max-files.d.ts +26 -0
  66. package/dist/form/validators/max-length.d.ts +33 -0
  67. package/dist/form/validators/max-total-file-size.d.ts +26 -0
  68. package/dist/form/validators/max.d.ts +26 -0
  69. package/dist/form/validators/min-age.d.ts +27 -0
  70. package/dist/form/validators/min-date.d.ts +25 -0
  71. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
  72. package/dist/form/validators/min-files.d.ts +27 -0
  73. package/dist/form/validators/min-length.d.ts +33 -0
  74. package/dist/form/validators/min.d.ts +26 -0
  75. package/dist/form/validators/multiple-of.d.ts +25 -0
  76. package/dist/form/validators/non-negative.d.ts +22 -0
  77. package/dist/form/validators/non-zero.d.ts +22 -0
  78. package/dist/form/validators/past-date.d.ts +23 -0
  79. package/dist/form/validators/pattern.d.ts +26 -0
  80. package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
  81. package/dist/form/validators/required.d.ts +29 -0
  82. package/dist/form/validators/url.d.ts +45 -0
  83. package/dist/form/validators/validation-issue.d.ts +8 -0
  84. package/dist/i18n/descriptor.d.ts +54 -0
  85. package/dist/i18n/en.json.d.ts +49 -0
  86. package/dist/i18n/loader.d.ts +71 -0
  87. package/dist/i18n/locale-store.d.ts +46 -0
  88. package/dist/i18n/locale.d.ts +90 -0
  89. package/dist/i18n/message-format.d.ts +136 -0
  90. package/dist/i18n/resolve-localized.d.ts +17 -0
  91. package/dist/i18n/ru.json.d.ts +49 -0
  92. package/dist/i18n/translator.d.ts +61 -0
  93. package/dist/i18n/validation-message.d.ts +53 -0
  94. package/dist/i18n.d.ts +26 -0
  95. package/dist/i18n.js +86 -0
  96. package/dist/index.d.ts +18 -6
  97. package/dist/index.js +932 -2779
  98. package/dist/loader-BWkigpVe.js +52 -0
  99. package/dist/locale/en.d.ts +2 -0
  100. package/dist/locale/en.js +5 -0
  101. package/dist/locale/en.json +45 -0
  102. package/dist/locale/index.d.ts +18 -0
  103. package/dist/locale/ru.d.ts +2 -0
  104. package/dist/locale/ru.js +44 -0
  105. package/dist/locale/ru.json +45 -0
  106. package/dist/locale-CG54AU2Q.js +219 -0
  107. package/dist/locale.d.ts +2 -0
  108. package/dist/locale.js +9 -0
  109. package/dist/{state → model}/behaviors-value.d.ts +24 -6
  110. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
  111. package/dist/{state → model}/derived-registry.d.ts +1 -1
  112. package/dist/model/index.d.ts +42 -0
  113. package/dist/model/model-nodes.d.ts +190 -0
  114. package/dist/model/model-signals-proxy.d.ts +63 -0
  115. package/dist/model/model-value-proxy.d.ts +84 -0
  116. package/dist/{state → model}/safe-effect.d.ts +50 -2
  117. package/dist/model/types.d.ts +239 -0
  118. package/dist/model.d.ts +2 -0
  119. package/dist/model.js +27 -0
  120. package/dist/node-kind-C-r6ZMdo.js +15 -0
  121. package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
  122. package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
  123. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  124. package/dist/{form → platforms/react}/hooks/types.d.ts +60 -38
  125. package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
  126. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  127. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
  128. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
  129. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
  130. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
  131. package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
  132. package/dist/platforms/react/i18n/context.d.ts +18 -0
  133. package/dist/platforms/react/i18n/hooks.d.ts +65 -0
  134. package/dist/platforms/react/i18n/provider.d.ts +59 -0
  135. package/dist/platforms/react/index.d.ts +30 -0
  136. package/dist/react.d.ts +22 -0
  137. package/dist/react.js +505 -0
  138. package/dist/runtime-token.d.ts +22 -0
  139. package/dist/safe-effect-BazLOv1g.js +669 -0
  140. package/dist/schema-controller-CpiO2JoK.js +143 -0
  141. package/dist/signals.d.ts +15 -9
  142. package/dist/signals.js +11 -6
  143. package/dist/strategy-DzvnsWLh.js +375 -0
  144. package/dist/type-guards-D-N_5Ddh.js +19 -0
  145. package/dist/validation-issue-B9mjI8jz.js +7 -0
  146. package/dist/validation-message-Ced9MGxs.js +194 -0
  147. package/dist/validation.d.ts +1 -1
  148. package/dist/validation.js +11 -165
  149. package/dist/validators/email.d.ts +1 -1
  150. package/dist/validators/email.js +5 -8
  151. package/dist/validators/future-date.d.ts +1 -1
  152. package/dist/validators/future-date.js +10 -13
  153. package/dist/validators/integer.d.ts +1 -1
  154. package/dist/validators/integer.js +4 -7
  155. package/dist/validators/is-date.d.ts +1 -1
  156. package/dist/validators/is-date.js +5 -8
  157. package/dist/validators/is-number.d.ts +1 -1
  158. package/dist/validators/is-number.js +4 -7
  159. package/dist/validators/max-age.d.ts +1 -1
  160. package/dist/validators/max-age.js +8 -12
  161. package/dist/validators/max-date.d.ts +1 -1
  162. package/dist/validators/max-date.js +10 -13
  163. package/dist/validators/max-length.d.ts +1 -1
  164. package/dist/validators/max-length.js +7 -10
  165. package/dist/validators/max.d.ts +1 -1
  166. package/dist/validators/max.js +4 -7
  167. package/dist/validators/min-age.d.ts +1 -1
  168. package/dist/validators/min-age.js +10 -11
  169. package/dist/validators/min-date.d.ts +1 -1
  170. package/dist/validators/min-date.js +8 -11
  171. package/dist/validators/min-length.d.ts +1 -1
  172. package/dist/validators/min-length.js +4 -7
  173. package/dist/validators/min.d.ts +1 -1
  174. package/dist/validators/min.js +4 -7
  175. package/dist/validators/multiple-of.d.ts +1 -1
  176. package/dist/validators/multiple-of.js +11 -10
  177. package/dist/validators/non-negative.d.ts +1 -1
  178. package/dist/validators/non-negative.js +2 -5
  179. package/dist/validators/non-zero.d.ts +1 -1
  180. package/dist/validators/non-zero.js +4 -7
  181. package/dist/validators/past-date.d.ts +1 -1
  182. package/dist/validators/past-date.js +10 -13
  183. package/dist/validators/pattern.d.ts +1 -1
  184. package/dist/validators/pattern.js +5 -8
  185. package/dist/validators/phone.d.ts +1 -1
  186. package/dist/validators/phone.js +10 -11
  187. package/dist/validators/required.d.ts +1 -1
  188. package/dist/validators/required.js +4 -7
  189. package/dist/validators/url.d.ts +1 -1
  190. package/dist/validators/url.js +48 -17
  191. package/dist/validators.d.ts +1 -5
  192. package/dist/validators.js +98 -154
  193. package/docs/llms/01-api-reference.md +89 -0
  194. package/docs/llms/02-quick-start.md +181 -0
  195. package/docs/llms/03-api-signatures.md +494 -0
  196. package/docs/llms/04-common-patterns.md +204 -0
  197. package/docs/llms/05-common-mistakes.md +192 -0
  198. package/docs/llms/06-troubleshooting.md +16 -0
  199. package/docs/llms/07-complete-import.md +130 -0
  200. package/docs/llms/08-form-types.md +54 -0
  201. package/docs/llms/09-formschema.md +191 -0
  202. package/docs/llms/10-arrays.md +241 -0
  203. package/docs/llms/11-async-watchfield.md +53 -0
  204. package/docs/llms/12-array-cleanup.md +31 -0
  205. package/docs/llms/13-multi-step.md +162 -0
  206. package/docs/llms/14-extended-mistakes.md +101 -0
  207. package/docs/llms/15-project-structure.md +279 -0
  208. package/docs/llms/16-ui-components.md +229 -0
  209. package/docs/llms/17-nonexistent-api.md +278 -0
  210. package/docs/llms/18-conditional-fields.md +222 -0
  211. package/docs/llms/19-reading-values.md +100 -0
  212. package/docs/llms/20-compute-vs-watch.md +153 -0
  213. package/docs/llms/21-array-operations.md +163 -0
  214. package/docs/llms/22-cycle-detection.md +86 -0
  215. package/docs/llms/23-copy-from.md +141 -0
  216. package/docs/llms/24-sync-fields.md +115 -0
  217. package/docs/llms/25-reset-when.md +122 -0
  218. package/docs/llms/26-transform-value.md +136 -0
  219. package/docs/llms/27-revalidate-when.md +133 -0
  220. package/docs/llms/28-submit-and-reset.md +409 -0
  221. package/docs/llms/29-async-preload.md +203 -0
  222. package/docs/llms/30-type-safety-recipes.md +213 -0
  223. package/docs/llms/31-async-validator-debounce.md +178 -0
  224. package/docs/llms/32-async-options-loading.md +108 -0
  225. package/docs/llms/33-validation-strategy.md +305 -0
  226. package/docs/llms/34-i18n.md +298 -0
  227. package/docs/llms/35-unified-contract.md +309 -0
  228. package/docs/llms/36-issue-messages.md +284 -0
  229. package/docs/llms/37-compiled-schema.md +332 -0
  230. package/docs/llms/38-form-flow.md +396 -0
  231. package/llms-index.json +1 -0
  232. package/llms.txt +10014 -3826
  233. package/package.json +77 -15
  234. package/dist/behaviors-node-DGEFMTQu.js +0 -18
  235. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  236. package/dist/date-utils-dTT_x_Be.js +0 -31
  237. package/dist/derived-registry-CluZCQ7U.js +0 -16
  238. package/dist/form/behaviors.d.ts +0 -133
  239. package/dist/form/error-handler.d.ts +0 -166
  240. package/dist/form/factories/index.d.ts +0 -6
  241. package/dist/form/factories/node-factory.d.ts +0 -188
  242. package/dist/form/form-submitter.d.ts +0 -125
  243. package/dist/form/nodes/array-node.d.ts +0 -360
  244. package/dist/form/nodes/field-node.d.ts +0 -294
  245. package/dist/form/nodes/form-node.d.ts +0 -340
  246. package/dist/form/nodes/group-node.d.ts +0 -288
  247. package/dist/form/nodes/model-array-node.d.ts +0 -121
  248. package/dist/form/status-machine.d.ts +0 -153
  249. package/dist/form/types/deep-schema.d.ts +0 -116
  250. package/dist/form/types/form-proxy.d.ts +0 -141
  251. package/dist/form/unique-id.d.ts +0 -53
  252. package/dist/form/validation/validators/date-utils.d.ts +0 -30
  253. package/dist/form/validation/validators/email.d.ts +0 -23
  254. package/dist/form/validation/validators/future-date.d.ts +0 -23
  255. package/dist/form/validation/validators/integer.d.ts +0 -22
  256. package/dist/form/validation/validators/is-date.d.ts +0 -23
  257. package/dist/form/validation/validators/is-number.d.ts +0 -24
  258. package/dist/form/validation/validators/max-age.d.ts +0 -25
  259. package/dist/form/validation/validators/max-date.d.ts +0 -25
  260. package/dist/form/validation/validators/max-files.d.ts +0 -26
  261. package/dist/form/validation/validators/max-length.d.ts +0 -27
  262. package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
  263. package/dist/form/validation/validators/max.d.ts +0 -25
  264. package/dist/form/validation/validators/min-age.d.ts +0 -25
  265. package/dist/form/validation/validators/min-date.d.ts +0 -25
  266. package/dist/form/validation/validators/min-files.d.ts +0 -26
  267. package/dist/form/validation/validators/min-length.d.ts +0 -27
  268. package/dist/form/validation/validators/min.d.ts +0 -25
  269. package/dist/form/validation/validators/multiple-of.d.ts +0 -24
  270. package/dist/form/validation/validators/non-negative.d.ts +0 -22
  271. package/dist/form/validation/validators/non-zero.d.ts +0 -22
  272. package/dist/form/validation/validators/past-date.d.ts +0 -23
  273. package/dist/form/validation/validators/pattern.d.ts +0 -33
  274. package/dist/form/validation/validators/required.d.ts +0 -32
  275. package/dist/form/validation/validators/url.d.ts +0 -43
  276. package/dist/form/validation-schema.d.ts +0 -106
  277. package/dist/form/validation-strategy.d.ts +0 -55
  278. package/dist/form/validators.d.ts +0 -6
  279. package/dist/form-model-DYCzGGKF.js +0 -286
  280. package/dist/state/index.d.ts +0 -31
  281. package/dist/state/subscription-manager.d.ts +0 -180
  282. package/dist/state/types.d.ts +0 -144
  283. package/dist/state.d.ts +0 -2
  284. package/dist/state.js +0 -21
  285. package/dist/subscription-manager-705bls97.js +0 -171
@@ -0,0 +1,30 @@
1
+ import { M as f, j as c, k as r, l } from "./safe-effect-BazLOv1g.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
+ };
@@ -0,0 +1,44 @@
1
+ const e = {
2
+ "format.fileSize.b": "{value} B",
3
+ "format.fileSize.kb": "{value} KB",
4
+ "format.fileSize.mb": "{value} MB",
5
+ "format.fileSize.gb": "{value} GB",
6
+ "format.fileSize.tb": "{value} TB",
7
+ "validation.required": "This field is required",
8
+ "validation.pattern": "Invalid format",
9
+ "validation.email": "Enter a valid email address",
10
+ "validation.url": "Enter a valid URL",
11
+ "validation.url_protocol": "The URL must use one of these protocols: {allowedProtocols}",
12
+ "validation.phone": "Enter a valid phone number",
13
+ "validation.min": "Must be at least {min}",
14
+ "validation.max": "Must be at most {max}",
15
+ "validation.minLength": "Enter at least {minLength, plural, one{# character} other{# characters}}",
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}}",
19
+ "validation.isNumber": "Enter a number",
20
+ "validation.integer": "Enter a whole number",
21
+ "validation.multipleOf": "Must be a multiple of {multipleOf}",
22
+ "validation.nonNegative": "Must not be negative",
23
+ "validation.nonZero": "Must not be zero",
24
+ "validation.date_invalid": "Enter a valid date",
25
+ "validation.date_min": "The date must not be earlier than {minDate}",
26
+ "validation.date_max": "The date must not be later than {maxDate}",
27
+ "validation.date_past": "The date must not be in the past",
28
+ "validation.date_future": "The date must not be in the future",
29
+ "validation.date_min_age": "Age must be at least {minAge}",
30
+ "validation.date_max_age": "Age must be at most {maxAge}",
31
+ "validation.fileType": "File {fileName} has a type that is not allowed. Allowed: {accept}",
32
+ "validation.maxFileSize": "File {fileName} is larger than {maxFileSize}",
33
+ "validation.minFileSize": "File {fileName} is smaller than {minFileSize}",
34
+ "validation.maxFiles": "No more than {maxFiles, plural, one{# file} other{# files}}",
35
+ "validation.minFiles": "At least {minFiles, plural, one{# file} other{# files}}",
36
+ "validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
37
+ "validation.fileExists": "File {fileName} has already been added",
38
+ "validation.uploadAborted": "Upload cancelled",
39
+ "validation.uploadFailed": "Failed to upload {fileName}",
40
+ "validation.ruleFailed": "Could not check this field. Try again"
41
+ };
42
+ export {
43
+ e as m
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;
@@ -0,0 +1,84 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ import { FormModel, ModelArray } from '../../model/types';
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>>;
20
+ /**
21
+ * Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
22
+ * Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
23
+ *
24
+ * Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
25
+ * - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
26
+ * - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
27
+ * МАТЕРИАЛИЗОВАН в форме (узел `{ model: model.$.<массив>, item }` в схеме) — тогда `form` строки = та же нода, что
28
+ * рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
29
+ * к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
30
+ *
31
+ * Работает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки
32
+ * можно снова вызвать `applyEach`.
33
+ *
34
+ * @example
35
+ * applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
36
+ * compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
37
+ * enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
38
+ * applyEach(row.$.phones, phoneBehavior); // массив внутри строки
39
+ * }));
40
+ */
41
+ export declare function applyEach<TItem extends object>(array: ArrayTarget<TItem>, itemSchema: FormBehavior<NoInfer<TItem>>): void;
42
+ /**
43
+ * Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
44
+ * Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
45
+ * новые строки с флагом false исключения не запускают.
46
+ *
47
+ * Начальные данные и добавленные строки флаг не МЕНЯЮТ — он приходит сразу `true`. Такая строка
48
+ * прежний выбор не снимает: флаг сбрасывается у неё. Из нескольких пришедших с `true` сразу
49
+ * (начальные данные) остаётся первая по порядку строк.
50
+ *
51
+ * @example
52
+ * exclusiveFlag(model.$.contacts, (row) => row.$.primary);
53
+ */
54
+ export declare function exclusiveFlag<TItem extends object>(array: ArrayTarget<TItem>, getFlag: (row: FormModel<NoInfer<TItem>>) => Signal<boolean>): void;
55
+ /**
56
+ * Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
57
+ * `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
58
+ * финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
59
+ * промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
60
+ *
61
+ * @example
62
+ * // последняя строка = 100 − Σ(остальные)
63
+ * aggregateInto(model.$.rows, (rows) => {
64
+ * const n = rows.length; if (n === 0) return [];
65
+ * const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
66
+ * return [{ index: n - 1, patch: { percent: 100 - others } }];
67
+ * });
68
+ */
69
+ export declare function aggregateInto<TItem extends object>(array: ArrayTarget<TItem>, derive: (rows: NoInfer<TItem>[]) => Array<{
70
+ index: number;
71
+ patch: Partial<NoInfer<TItem>>;
72
+ }>): 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 {};
@@ -0,0 +1,56 @@
1
+ import { BehaviorCleanup } from '../../model/behaviors-value';
2
+ import { SchemaController } from '../schema-controller';
3
+ import { BehaviorScope, FormBehavior } from './types';
4
+ import { FormFlow } from '../flow/flow';
5
+ /** Зарегистрировать произвольную отписку в активной схеме. */
6
+ export declare function onDispose(cleanup: BehaviorCleanup): void;
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 для кросс-операторов). */
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;
36
+ /**
37
+ * Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
42
+ * compute(model.$.total, () => model.price * model.qty);
43
+ * enableWhen([model.$.city], () => Boolean(model.country));
44
+ * onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
45
+ * hideWhen(schema.node('delivery'), () => model.country === '');
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
50
+ /** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
51
+ export declare function effect(fn: () => void | (() => void)): void;
52
+ /**
53
+ * Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». Внутри схемы
54
+ * поведения (в `setup`, в её effect'ах и отложенных заданиях) запись отменяется снятием схемы.
55
+ */
56
+ export declare function defer(fn: () => void): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
3
+ *
4
+ * Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
5
+ * `defineFormBehavior(({ model, form, schema }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
6
+ * автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
7
+ * владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
8
+ * ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
9
+ *
10
+ * Граница ответственности: контракт НЕ управляет валидацией — это отдельный слой
11
+ * (`defineValidation` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
12
+ * владельцем `issues`. Это сделано намеренно. Для редких крайних случаев
13
+ * behavior-driven валидации (cross-field, async-uniqueness) можно написать собственный оператор поверх
14
+ * `effect`/`onChange` + `node.setIssues(...)` — он будет неотличим от встроенного; примеры см. в
15
+ * tests/behaviors/web-scenarios (W1/W3). В общем случае правила валидности держите в слое валидации.
16
+ *
17
+ * Модуль разложен по зонам ответственности: `./types` — контракт схемы, `./context` — ambient-сток
18
+ * и низкоуровневый набор авторинга, `./internals` — чистые утилиты, `./operators` — операторы над
19
+ * полем и группой, `./collections` — операторы над массивами и под-моделями.
20
+ *
21
+ * @group Behaviors
22
+ * @module form/behaviors
23
+ */
24
+ export type { BehaviorScope, FormBehavior, ChangeContext } from './types';
25
+ export type { Signal, ReadonlySignal, BehaviorCleanup } from './types';
26
+ export { onDispose, getScope, defineFormBehavior, effect, defer } from './context';
27
+ export { compute, computeFrom, copyFrom, onChange, enableWhen, disableWhen, transformValue, resetWhen, syncFields, revalidateWhen, } from './operators';
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';
@@ -0,0 +1,41 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ /** Групповой узел дерева `$`: набор дочерних сигналов плюс служебный путь. */
3
+ export type GroupSignals = Record<string, unknown> & {
4
+ __path?: string;
5
+ };
6
+ /**
7
+ * Лист модели, а не контейнерный узел дерева `$`.
8
+ *
9
+ * Узлы-группы/массивы тоже структурно совместимы с `ReadonlySignal` (у них есть `peek`/`value`/
10
+ * `subscribe` над агрегатом поддерева), поэтому одного duck-typing по `peek` мало: без отсечки по
11
+ * `isModelContainerSignal` группа уходила бы в скалярные ветки операторов — `enableWhen` молча
12
+ * терял бы `enableGroup`, `apply` получал бы значение вместо под-модели, а `copyFrom` — запись в
13
+ * read-only агрегат вместо рекурсивного `writeGroup`.
14
+ */
15
+ export declare const isLeafSignal: (v: unknown) => v is Signal<unknown>;
16
+ /**
17
+ * Ручка значения целиком: лист либо узел-массив дерева `$`.
18
+ *
19
+ * Массив копируется и читается ЗНАЧЕНИЕМ — заменой целиком, а не обходом по ключам: у его узла
20
+ * собственный ключ один (`length`), и обход, годный для группы, отдавал бы вместо массива мусор.
21
+ * Для `enableWhen` массив при этом остаётся НЕ листом ({@link isLeafSignal}): его нода ищется по
22
+ * пути, а она бывает и полем, и массивом под-форм.
23
+ */
24
+ export declare const isValueHandle: (v: unknown) => v is Signal<unknown>;
25
+ export declare const asArray: <X>(v: X | X[]) => X[];
26
+ export declare function readGroup(g: GroupSignals): Record<string, unknown>;
27
+ /**
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 (без
37
+ * стабилизации) preact обрывает невнятным «Cycle detected» — перехватываем и заменяем понятной
38
+ * ошибкой с именем поля и подсказкой. Сходящиеся compute (упираются в peek-guard) и массовые
39
+ * синхронные мутации цикла не порождают → не затрагиваются.
40
+ */
41
+ export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
@@ -1,11 +1,34 @@
1
1
  import { ReadonlySignal } from '@preact/signals-core';
2
- import { BehaviorCleanup } from '../state/behaviors-value';
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,11 +37,11 @@ import { BehaviorCleanup } from '../state/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
  *
21
- * @group Model
44
+ * @group Form
22
45
  * @example
23
46
  * ```typescript
24
47
  * enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
@@ -38,7 +61,7 @@ export declare function enableWhen(target: ReadonlySignal<unknown>, condition: (
38
61
  * сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
39
62
  * `condition` реактивен (читает свои сигналы модели).
40
63
  *
41
- * @group Model
64
+ * @group Form
42
65
  * @param target Сигнал-цель поля (`model.$.<path>`).
43
66
  * @param condition Реактивное условие; при `true` поле выключается.
44
67
  * @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
@@ -0,0 +1,59 @@
1
+ import { Signal, ReadonlySignal } from '@preact/signals-core';
2
+ import { ResetWhenOptionsArg } from '../../model/behaviors-value';
3
+ import { ChangeContext } from './types';
4
+ /** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
5
+ export declare function compute<R>(target: Signal<R>, read: () => NoInfer<R>, options?: {
6
+ when?: () => boolean;
7
+ }): void;
8
+ /** Вычисляемое поле с явным списком зависимостей (escape hatch). */
9
+ export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => NoInfer<R>, options?: {
10
+ when?: (...values: any[]) => boolean;
11
+ }): void;
12
+ /**
13
+ * Копирование `source → target` — скаляр или группа (объект целиком).
14
+ *
15
+ * Группы разной формы копируются как `set` группы модели: поля цели, которых нет в значении,
16
+ * остаются прежними; в dev об этом одно предупреждение на оператор. Значение цели целиком
17
+ * собирает `transform`.
18
+ */
19
+ export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
20
+ when?: () => boolean;
21
+ transform?: (value: T) => T;
22
+ }): void;
23
+ /**
24
+ * Реакция на изменение поля; `{ debounce, immediate }`.
25
+ *
26
+ * Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
27
+ * (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
28
+ *
29
+ * Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
30
+ * предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
31
+ * `signal.aborted` перед применением результата — это убирает гонки устаревших ответов.
32
+ */
33
+ export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
34
+ immediate?: boolean;
35
+ debounce?: number;
36
+ }): void;
37
+ type EnableTarget = ReadonlySignal<unknown> | object;
38
+ /** Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию. */
39
+ export declare function enableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
40
+ resetOnDisable?: boolean;
41
+ }): void;
42
+ /** Условное выключение поля(ей) (инверсия {@link enableWhen}). */
43
+ export declare function disableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
44
+ resetOnDisable?: boolean;
45
+ }): void;
46
+ /** Трансформация значения поля (идемпотентная). */
47
+ export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): 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;
53
+ /** Двусторонняя синхронизация двух полей. */
54
+ export declare function syncFields<T>(a: Signal<T>, b: Signal<NoInfer<T>>, options?: {
55
+ transform?: (value: T) => T;
56
+ }): void;
57
+ /** Ревалидация при изменении зависимостей. */
58
+ export declare function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
59
+ export {};
@@ -0,0 +1,83 @@
1
+ import { SchemaNodeControl, SchemaScope } from '../schema-controller';
2
+ /**
3
+ * Скрывать узел, пока условие истинно.
4
+ *
5
+ * Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
6
+ * Принудительное `schema.node(selector).setHidden(...)` главнее условия.
7
+ *
8
+ * Только видимость: поля скрытого узла не выключаются и проверяются правилами — переход шага
9
+ * откажет без замечаний на экране. Поля узла выключай тем же условием через `enableWhen`.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const mortgage = () => model.loanType === 'mortgage';
14
+ * enableWhen([model.$.propertyValue, model.$.initialPayment], mortgage, { resetOnDisable: true });
15
+ * hideWhen(schema.node('mortgage'), () => !mortgage());
16
+ * ```
17
+ */
18
+ export declare function hideWhen(node: SchemaNodeControl, condition: () => boolean): void;
19
+ /**
20
+ * Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
21
+ *
22
+ * Обработчик получает те же аргументы, что и исходный проп компонента. У контейнера он перекрывает
23
+ * одноимённый проп из `componentProps`. У поля и массива событие уже занято привязкой (запись
24
+ * значения в форму, кнопки массива) — обработчик вызывается после неё, а не вместо: ввод не ломается.
25
+ * У подформы своего компонента нет — правило не действует, в dev рендерер предупреждает.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
30
+ * await submitApplication(model.get());
31
+ * });
32
+ * ```
33
+ */
34
+ export declare function onComponentEvent(node: SchemaNodeControl, event: string, handler: (...args: any[]) => any): void;
35
+ /**
36
+ * Реактивный эффект, живущий, пока схема области смонтирована рендерером.
37
+ *
38
+ * В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
39
+ * (`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const panel = schema.node('summary').getRef<HTMLElement>();
44
+ * renderEffect(schema, () => {
45
+ * if (model.loanType === 'mortgage') panel.current?.scrollIntoView();
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void;
50
+ /**
51
+ * Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
52
+ * хук, способный повлиять на первый рендер, — внутри можно править пропсы через
53
+ * `schema.node(selector).patchProps({ … })`.
54
+ *
55
+ * К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
56
+ * {@link onMount} / {@link onUnmount}.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * onInit(schema.node('summary'), () => {
61
+ * schema.node('summary').patchProps({ currency: detectCurrency() });
62
+ * });
63
+ * ```
64
+ */
65
+ export declare function onInit(_node: SchemaNodeControl, fn: () => void): void;
66
+ /**
67
+ * Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * onMount(schema.node('data-boundary'), () => void loadApplication());
72
+ * ```
73
+ */
74
+ export declare function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void;
75
+ /**
76
+ * Хук перед размонтированием узла.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
81
+ * ```
82
+ */
83
+ export declare function onUnmount(node: SchemaNodeControl, fn: () => void): void;