@reformer/core 11.0.0-beta.3 → 11.0.0-beta.5

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 (130) hide show
  1. package/dist/behaviors.d.ts +1 -1
  2. package/dist/behaviors.js +37 -37
  3. package/dist/create-model-Dxh9cfLp.js +97 -0
  4. package/dist/form/behaviors/collections.d.ts +50 -0
  5. package/dist/form/behaviors/context.d.ts +23 -0
  6. package/dist/form/behaviors/index.d.ts +28 -0
  7. package/dist/form/behaviors/internals.d.ts +26 -0
  8. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
  9. package/dist/form/behaviors/operators.d.ts +51 -0
  10. package/dist/form/behaviors/types.d.ts +17 -0
  11. package/dist/form/create-core-form.d.ts +3 -3
  12. package/dist/form/create-form.d.ts +12 -10
  13. package/dist/form/factories/node-factory.d.ts +17 -16
  14. package/dist/form/index.d.ts +10 -16
  15. package/dist/form/nodes/array-node.d.ts +3 -3
  16. package/dist/form/nodes/field-node.d.ts +6 -3
  17. package/dist/form/nodes/group-node.d.ts +7 -7
  18. package/dist/form/nodes/model-array-node.d.ts +3 -3
  19. package/dist/form/types/contracts.d.ts +9 -6
  20. package/dist/form/types/deep-schema.d.ts +5 -5
  21. package/dist/form/types/validation-schema.d.ts +1 -1
  22. package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
  23. package/dist/form/validation/context.d.ts +27 -0
  24. package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
  25. package/dist/form/validation/index.d.ts +23 -28
  26. package/dist/form/validation/operators.d.ts +42 -0
  27. package/dist/form/validation/run.d.ts +45 -0
  28. package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
  29. package/dist/form/validation/types.d.ts +28 -0
  30. package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
  31. package/dist/form/{validation/validators → validators}/email.d.ts +6 -7
  32. package/dist/form/{validation/validators → validators}/file-type.d.ts +6 -7
  33. package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
  34. package/dist/form/{validation/validators → validators}/future-date.d.ts +6 -7
  35. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  36. package/dist/form/{validation/validators → validators}/integer.d.ts +6 -7
  37. package/dist/form/{validation/validators → validators}/is-date.d.ts +6 -7
  38. package/dist/form/{validation/validators → validators}/is-number.d.ts +6 -7
  39. package/dist/form/{validation/validators → validators}/max-age.d.ts +6 -7
  40. package/dist/form/{validation/validators → validators}/max-date.d.ts +6 -7
  41. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +6 -7
  42. package/dist/form/{validation/validators → validators}/max-files.d.ts +6 -7
  43. package/dist/form/{validation/validators → validators}/max-length.d.ts +7 -8
  44. package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +6 -7
  45. package/dist/form/{validation/validators → validators}/max.d.ts +7 -8
  46. package/dist/form/{validation/validators → validators}/min-age.d.ts +6 -7
  47. package/dist/form/{validation/validators → validators}/min-date.d.ts +6 -7
  48. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +6 -7
  49. package/dist/form/{validation/validators → validators}/min-files.d.ts +6 -7
  50. package/dist/form/{validation/validators → validators}/min-length.d.ts +7 -8
  51. package/dist/form/{validation/validators → validators}/min.d.ts +7 -8
  52. package/dist/form/{validation/validators → validators}/multiple-of.d.ts +6 -7
  53. package/dist/form/{validation/validators → validators}/non-negative.d.ts +6 -7
  54. package/dist/form/{validation/validators → validators}/non-zero.d.ts +6 -7
  55. package/dist/form/{validation/validators → validators}/past-date.d.ts +6 -7
  56. package/dist/form/{validation/validators → validators}/pattern.d.ts +7 -15
  57. package/dist/form/{validation/validators → validators}/phone.d.ts +6 -7
  58. package/dist/form/{validation/validators → validators}/required.d.ts +8 -13
  59. package/dist/form/{validation/validators → validators}/url.d.ts +8 -13
  60. package/dist/index.d.ts +7 -4
  61. package/dist/index.js +272 -94
  62. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
  63. package/dist/{state → model}/index.d.ts +10 -11
  64. package/dist/model/model-nodes.d.ts +60 -0
  65. package/dist/model/model-signals-proxy.d.ts +32 -0
  66. package/dist/model/model-value-proxy.d.ts +3 -0
  67. package/dist/{state → model}/safe-effect.d.ts +1 -1
  68. package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
  69. package/dist/model.d.ts +2 -0
  70. package/dist/model.js +21 -0
  71. package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
  72. package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
  73. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
  74. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
  75. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
  76. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
  77. package/dist/platforms/react/index.d.ts +23 -0
  78. package/dist/signals.d.ts +1 -1
  79. package/dist/strategy-DA3JwxsR.js +170 -0
  80. package/dist/validation.d.ts +1 -1
  81. package/dist/validation.js +10 -165
  82. package/dist/validators/email.d.ts +1 -1
  83. package/dist/validators/future-date.d.ts +1 -1
  84. package/dist/validators/integer.d.ts +1 -1
  85. package/dist/validators/is-date.d.ts +1 -1
  86. package/dist/validators/is-number.d.ts +1 -1
  87. package/dist/validators/max-age.d.ts +1 -1
  88. package/dist/validators/max-date.d.ts +1 -1
  89. package/dist/validators/max-length.d.ts +1 -1
  90. package/dist/validators/max.d.ts +1 -1
  91. package/dist/validators/min-age.d.ts +1 -1
  92. package/dist/validators/min-date.d.ts +1 -1
  93. package/dist/validators/min-length.d.ts +1 -1
  94. package/dist/validators/min.d.ts +1 -1
  95. package/dist/validators/multiple-of.d.ts +1 -1
  96. package/dist/validators/non-negative.d.ts +1 -1
  97. package/dist/validators/non-zero.d.ts +1 -1
  98. package/dist/validators/past-date.d.ts +1 -1
  99. package/dist/validators/pattern.d.ts +1 -1
  100. package/dist/validators/phone.d.ts +1 -1
  101. package/dist/validators/required.d.ts +1 -1
  102. package/dist/validators/url.d.ts +1 -1
  103. package/dist/validators.d.ts +1 -1
  104. package/docs/llms/02-quick-start.md +6 -6
  105. package/docs/llms/03-api-signatures.md +3 -3
  106. package/docs/llms/05-common-mistakes.md +1 -1
  107. package/docs/llms/09-formschema.md +11 -11
  108. package/docs/llms/10-arrays.md +4 -4
  109. package/docs/llms/14-extended-mistakes.md +1 -1
  110. package/docs/llms/15-project-structure.md +8 -7
  111. package/docs/llms/16-ui-components.md +6 -6
  112. package/docs/llms/17-nonexistent-api.md +2 -2
  113. package/docs/llms/29-async-preload.md +4 -4
  114. package/docs/llms/30-type-safety-recipes.md +1 -1
  115. package/llms-index.json +1 -0
  116. package/llms.txt +2102 -262
  117. package/package.json +5 -4
  118. package/dist/form/behaviors.d.ts +0 -133
  119. package/dist/form/validation-schema.d.ts +0 -106
  120. package/dist/form/validators.d.ts +0 -11
  121. package/dist/form-model-DhW2HZOB.js +0 -323
  122. package/dist/state.d.ts +0 -2
  123. package/dist/state.js +0 -21
  124. package/dist/subscription-manager-705bls97.js +0 -171
  125. /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
  126. /package/dist/{state → model}/behaviors-value.d.ts +0 -0
  127. /package/dist/{state → model}/derived-registry.d.ts +0 -0
  128. /package/dist/{state → model}/types.d.ts +0 -0
  129. /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
  130. /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"schemaVersion":1,"package":"@reformer/core","version":"6.0.0","topics":[{"id":"api-reference","file":"01-api-reference.md","title":"api-reference","purpose":"Архитектура M1: значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Behaviors работают на сигналах (`model.$.field`), а не на строковых путях.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"1. API Reference","slug":"api-reference"}],"terms":[["reformer",18],["core",18],["behaviors",6],["value",6],["model",5],["useformcontrolvalue",5],["loantype",5],["createform",4],["validate",4],["useformcontrol",4],["type",4],["field",3],["validation",3],["computefrom",3],["copyfrom",3],["enablewhen",3],["примитивы",3],["required",3],["min",3],["validators",3],["null",3],["только",3],["form",3],["const",3],["control",3],["critically",2],["important",2],["живут",2],["модели",2],["createmodel",2],["ноды",2],["validatemodel",2],["definevalidationschema",2],["apply",2],["rule",2],["usearraylength",2],["validationerror",2],["watchfield",2],["disablewhen",2],["transformvalue",2],["resetwhen",2],["syncfields",2],["revalidatewhen",2],["email",2],["defineformbehavior",2],["compute",2],["сигналы",2],["возвращают",2],["cleanup",2],["сам",2],["schema",2],["опциональные",2],["number",2],["string",2],["errors",2],["disabled",2],["поля",2],["напрямую",2],["длина",2],["массива",2],["correct",2]]},{"id":"quick-start","file":"02-quick-start.md","title":"quick-start","purpose":"Архитектура M1: **модель данных** — источник истины, схема привязывает поля к её сигналам (`model.$.field`), а сборка идёт ОДНИМ вызовом `createCoreForm` (для рендера ui-kit; в рендерерах — `createReactForm` / `createJsonForm`), который создаёт модель, строит форму, запускает поведение и собирает валидацию. Layout-схема НЕ несёт валидаторов — валидация живёт в отдельной схеме `defineValidationSchema` из `@reformer/core/validation` и запускается внешним раннером `validateModel(model, schema)`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"1.5 QUICK START - Minimal Working Form","slug":"quick-start-minimal-working-form"}],"terms":[["model",23],["form",19],["type",15],["schema",13],["email",13],["name",12],["const",11],["componentprops",10],["item",10],["component",9],["reformer",9],["label",8],["formfield",7],["properties",7],["kit",5],["field",5],["core",5],["validation",5],["required",5],["contactform",5],["value",5],["array",5],["propertyitem",5],["схеме",4],["control",4],["createcoreform",4],["validatemodel",4],["import",4],["from",4],["validate",4],["inputfield",4],["react",4],["модели",4],["под",4],["description",4],["estimatedvalue",4],["options",3],["поля",3],["jsx",3],["один",3],["props",3],["модель",3],["схема",3],["валидация",3],["definevalidationschema",3],["createmodel",3],["button",3],["string",3],["contactvalidation",3],["message",3],["submit",3],["testid",3],["элемента",3],["myform",3],["driven",2],["его",2],["placeholder",2],["объявляются",2],["без",2],["свои",2],["input",2],["find_recipe",2],["topic",2],["источник",2],["истины",2],["сборка",2],["одним",2],["вызовом",2],["для",2],["который",2],["строит",2],["валидацию",2],["layout",2],["отдельной",2],["typescript",2],["useformbundle",2],["formproxy",2],["validators",2],["not",2],["your",2],["ноды",2],["раз",2],["contact",2],["function",2],["handlesubmit",2],["async",2],["только",2],["return",2],["components",2],["formstepprops",2],["через",2],["arrays",2],["formmodel",2],["apartment",2],["number",2]]},{"id":"api-signatures","file":"03-api-signatures.md","title":"api-signatures","purpose":"// Форма (ноды поверх сигналов модели) createForm<T>({ model, schema, behavior? }): FormProxy<T> // form.<field> → FieldNode / GroupNode / FormArrayProxy // form.<field>.setValue(v) / .value.value / .errors.value / .disabled.value // form.<field>.enable() / .disable() / .reset() / .markAsTouched() / .setErrors([...]) // form.<field>.updateComponentProps({ ... })","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"2. API SIGNATURES","slug":"api-signatures"}],"terms":[["model",64],["value",29],["options",29],["string",29],["message",25],["validate",20],["const",19],["reformer",17],["core",17],["boolean",15],["email",15],["void",15],["required",14],["form",13],["cross",13],["field",12],["validatemodel",12],["null",12],["typescript",11],["number",11],["signal",11],["definevalidationschema",10],["import",10],["from",10],["createform",9],["schema",9],["apply",9],["item",8],["validation",8],["validators",8],["над",8],["rule",8],["min",8],["правила",8],["type",8],["each",7],["validationschema",6],["sig",6],["внутри",6],["age",6],["validatewhen",6],["confirm",6],["readonlysignal",6],["target",6],["enablewhen",6],["formmodel",5],["get",5],["только",5],["layout",5],["схема",5],["моделью",5],["validationerror",5],["date",5],["поля",5],["amount",5],["под",5],["операторы",5],["never",5],["asyncrule",5],["per",5],["country",5],["length",4],["модели",4],["behavior",4],["seterrors",4],["валидация",4],["раннер",4],["ошибки",4],["promise",4],["валидации",4],["await",4],["scope",4],["root",4],["validateasync",4],["async",4],["password",4],["code",4],["return",4],["при",4],["для",4],["loanform",4],["computefrom",4],["copyfrom",4],["resetondisable",4],["revalidatewhen",4],["compute",4],["api",3],["модель",3],["createmodel",3],["enable",3],["дерево",3],["несёт",3],["функция",3],["внешний",3],["submit",3],["схемы",3],["max",3],["loan",3],["component",3],["inputfield",3],["abortsignal",3],["cond",3],["условная",3],["ветка",3],["это",3],["поведение",3],["снапшот",3],["массива",3],["композиция",3],["той",3],["signup",3],["loantype",3],["step1",3],["step5",3],["income",3],["formvalidation",3],["step_schemas",3],["behaviors",3],["when",3],["condition",3],["city",3],["true",3],["onchange",3],["данных",2],["reset",2],["сигналу",2],["modelarray",2],["ноды",2],["поверх",2],["errors",2],["updatecomponentprops",2],["отдельный",2],["контракт",2],["без",2],["формы",2],["warning",2],["блокирует",2],["устаревший",2],["прогон",2],["отменяется",2],["валидацию",2],["фабрики",2],["возвращают",2],["правило",2],["принимают",2],["validator",2],["factories",2],["years",2],["отдельной",2],["схеме",2],["loanvalidation",2],["сами",2],["обычная",2],["текущего",2],["прогона",2],["ниже",2],["ctx",2],["rules",2],["активны",2],["гасятся",2],["err",2],["получает",2],["arr",2],["schemas",2],["only",2],["strongpassword",2],["вешается",2],["через",2],["passwordsmatch",2],["emailunique",2],["res",2],["ветки",2],["элемента",2],["step",2],["mortgage",2],["полная",2],["cleanup",2],["vals",2],["source",2],["transform",2],["immediate",2],["disablewhen",2],["transformvalue",2],["resetwhen",2],["syncfields",2],["cleanups",2],["price",2],["quantity",2],["total",2],["dsl",2],["defineformbehavior",2]]},{"id":"common-patterns","file":"04-common-patterns.md","title":"common-patterns","purpose":"Все паттерны — на архитектуре M1: значения в модели (`model.$.field`), поведение (`defineFormBehavior`) на живых сигналах, валидация — отдельной ambient-схемой (`defineValidationSchema`, прогон по требованию через `validateModel`). Слои раздельны: layout НЕ несёт validators.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"3. COMMON PATTERNS","slug":"common-patterns"}],"terms":[["model",42],["reformer",15],["core",15],["import",14],["from",14],["const",14],["cross",12],["defineformbehavior",10],["typescript",10],["field",9],["value",9],["validation",9],["validatemodel",7],["myform",7],["initialpayment",7],["модели",6],["definevalidationschema",6],["propertyvalue",6],["compute",6],["validate",6],["null",6],["валидация",5],["loantype",5],["async",5],["onchange",5],["для",5],["form",5],["region",5],["schema",5],["правило",5],["required",5],["поведение",4],["enablewhen",4],["при",4],["внутри",4],["behaviors",4],["behavior",4],["computefrom",4],["price",4],["options",4],["поля",4],["return",4],["email",4],["get",4],["validationerror",4],["rule",4],["validatewhen",4],["revalidatewhen",4],["через",3],["validators",3],["reset",3],["mortgage",3],["signal",3],["await",3],["react",3],["submit",3],["ошибки",3],["формы",3],["false",3],["ошибка",3],["под",3],["min",3],["initialpaymentvsproperty",3],["validateadultage",3],["требованию",2],["layout",2],["conditional",2],["auto",2],["поле",2],["initial",2],["resetondisable",2],["true",2],["computed",2],["сигнал",2],["изменении",2],["источников",2],["источники",2],["кросс",2],["total",2],["quantity",2],["qty",2],["fullname",2],["personaldata",2],["boolean",2],["вне",2],["effect",2],["ноды",2],["abortsignal",2],["city",2],["updatecomponentprops",2],["cities",2],["debounce",2],["без",2],["напрямую",2],["снимок",2],["есть",2],["блокирующая",2],["multi",2],["step",2],["схемы",2],["шаг",2],["validateall",2],["получает",2],["type",2],["снапшот",2],["читается",2],["как",2],["code",2],["message",2],["item",2],["модель",2],["правила",2],["мост",2],["схема",2],["age",2],["new",2],["date",2],["getfullyear",2]]},{"id":"common-mistakes","file":"05-common-mistakes.md","title":"common-mistakes","purpose":"- Модель/форма/хуки/типы (`FormModel`, `ValidationError`)/**примитивы behaviors** — из `@reformer/core`. - Схема валидации (`defineValidationSchema` + операторы `validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`) и раннер `validateModel` — из `@reformer/core/validation`. - Чистые фабрики валидаторов (`required`/`min`/`email`/…) — из `@reformer/core/validators`. - Декларативный DSL поведения (`defineFormBehavior` + операторы) — из `@reformer/core/behaviors`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"4. COMMON MISTAKES","slug":"common-mistakes"}],"terms":[["model",23],["email",17],["value",14],["reformer",13],["core",13],["type",13],["const",11],["cross",9],["form",9],["validate",8],["typescript",8],["amount",8],["required",7],["behaviors",6],["correct",6],["import",6],["useformcontrolvalue",6],["from",6],["loantype",6],["null",6],["ctx",6],["definevalidationschema",5],["validatemodel",5],["validators",5],["price",5],["validateasync",4],["dsl",4],["через",4],["wrong",4],["total",4],["rate",4],["validationerror",3],["схема",3],["операторы",3],["раннер",3],["validation",3],["min",3],["useformcontrol",3],["computefrom",3],["enablewhen",3],["compute",3],["для",3],["get",3],["submit",3],["control",3],["api",3],["path",3],["quantity",3],["values",3],["оператор",3],["layout",3],["schema",3],["myform",3],["message",3],["await",3],["field",3],["interface",3],["string",3],["instanceof",3],["rule",2],["errors",2],["модель",2],["форма",2],["хуки",2],["formmodel",2],["примитивы",2],["валидации",2],["validatewhen",2],["each",2],["apply",2],["фабрики",2],["декларативный",2],["поведения",2],["defineformbehavior",2],["createform",2],["отдельный",2],["слой",2],["validationschema",2],["max",2],["либо",2],["onchange",2],["watchfield",2],["значения",2],["модели",2],["ноды",2],["returns",2],["сигналы",2],["пути",2],["старый",2],["удалён",2],["city",2],["boolean",2],["country",2],["qty",2],["валидаторы",2],["валидация",2],["json",2],["прогон",2],["внутри",2],["emailschema",2],["signal",2],["res",2],["free",2],["code",2],["setfieldvalue",2],["root",2],["amountvsmax",2],["maxamount",2],["export",2],["propertyitem",2],["propertytype",2],["description",2],["proxy",2],["node",2],["isfieldnode",2]]},{"id":"troubleshooting","file":"06-troubleshooting.md","title":"troubleshooting","purpose":"","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"5. TROUBLESHOOTING","slug":"troubleshooting"}],"terms":[["reformer",4],["core",4],["model",3],["string",2],["message",2],["вместо",2],["useformcontrolvalue",2],["без",2],["поле",2],["cycle",2],["модели",2],["field",2],["ошибки",2],["submit",2],["validatemodel",2],["schema",2]]},{"id":"complete-import","file":"07-complete-import.md","title":"complete-import","purpose":"// Типы import type { FormModel, // реактивная модель данных FormProxy, // тип формы для props компонентов FieldNode, // узел одного поля GroupNode, // узел группы ArrayNode, // узел массива ModelArray, // реактивный массив модели (push/removeAt/at/map/length) ModelSignals, // дерево сигналов ($) PathAwareSignal, // сигнал, знающий свой путь ValidationError, FieldConfig, // { value, component, componentProps?, ... } — layout, БЕЗ валидаторов FormSchema, FieldControlState, } from '@reformer/core';","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"Import Patterns","slug":"import-patterns"}],"terms":[["import",7],["reformer",7],["core",7],["type",7],["from",6],["behaviors",5],["string",5],["типы",4],["узел",3],["validationerror",3],["value",3],["validation",3],["form",3],["shape",3],["typescript",2],["модель",2],["хуки",2],["примитивы",2],["фабрики",2],["createform",2],["computefrom",2],["copyfrom",2],["enablewhen",2],["disablewhen",2],["transformvalue",2],["resetwhen",2],["syncfields",2],["revalidatewhen",2],["тип",2],["groupnode",2],["группы",2],["arraynode",2],["массива",2],["раннер",2],["validate",2],["apply",2],["validatemodel",2],["promise",2],["null",2],["validators",2],["interface",2],["export",2]]},{"id":"form-types","file":"08-form-types.md","title":"form-types","purpose":"Form-shape объявляй через `type`-alias (не `interface` — см. `30-type-safety-recipes.md`). Тип описывает форму ДАННЫХ (то, что кладётся в `createModel`), без узлов/сигналов.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"7. FORM TYPE DEFINITION","slug":"form-type-definition"}],"terms":[["string",8],["null",8],["type",5],["form",3],["name",3],["definition",2],["createmodel",2],["myform",2],["fields",2],["email",2],["phone",2],["age",2],["number",2],["status",2],["active",2],["objects",2],["address",2],["street",2],["city",2],["arrays",2],["model",2],["items",2]]},{"id":"formschema","file":"09-formschema.md","title":"formschema","purpose":"Под M1 схема **привязывает поле к сигналу модели** (`value: model.$.field`) и держит UI-конфиг (`component`/`componentProps`). Значения принадлежат модели. Валидаторов в layout-схеме НЕТ — правила живут в отдельной схеме `defineValidationSchema` из `@reformer/core/validation` и запускаются раннером `validateModel(model, schema)`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"8. SCHEMA FORMAT (CRITICALLY IMPORTANT)","slug":"schema-format-critically-important"}],"terms":[["model",21],["value",16],["component",12],["componentprops",12],["label",12],["name",11],["schema",10],["const",10],["item",10],["модели",9],["reformer",9],["inputfield",9],["form",9],["core",8],["formmodel",8],["typescript",7],["import",7],["createform",7],["from",7],["address",7],["city",7],["myform",6],["под",5],["type",5],["age",4],["agree",4],["status",4],["items",4],["поле",3],["field",3],["definevalidationschema",3],["node",3],["disabled",3],["это",3],["active",3],["street",3],["zip",3],["array",3],["proxy",3],["important",2],["схема",2],["схеме",2],["validation",2],["validatemodel",2],["сигнал",2],["обязателен",2],["placeholder",2],["options",2],["number",2],["узла",2],["состояние",2],["createmodel",2],["validate",2],["required",2],["selectfield",2],["checkboxfield",2],["myvalidation",2],["inactive",2],["sub",2],["api",2],["builder",2],["элемента",2],["массива",2],["addressnodes",2],["itemschema",2],["behavior",2],["доступ",2],["через",2],["тип",2],["formschema",2],["fieldnode",2],["string",2],["isfieldnode",2]]},{"id":"arrays","file":"10-arrays.md","title":"arrays","purpose":"Массивы объектов — **model-owned**: данные принадлежат модели (`model.arrayField` — это `ModelArray<Item>` с реактивными `push`/`removeAt`/`length`). В схеме массив объявляется узлом `{ array: model.<path>, item: (itemModel) => subSchema }`, где `item` строит под-схему одного элемента из его под-модели (`FormModel<Item>`).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":["[03-api-signatures.md](./03-api-signatures.md) — сигнатуры нод `form.<array>` / `ModelArray`, per-item валидация `each`","CDK / ui-kit form-array (`FormArray.Root`, `FormArraySection`) — `find_recipe(topic=\"form-array\")`"],"sections":[{"heading":"9. ARRAY SCHEMA FORMAT","slug":"array-schema-format"},{"heading":"See also","slug":"see-also"}],"terms":[["item",36],["model",34],["items",23],["array",17],["form",15],["name",10],["type",8],["reformer",8],["price",8],["myform",8],["properties",8],["под",7],["createform",7],["validation",7],["typescript",6],["const",6],["через",6],["each",6],["control",6],["schema",5],["push",5],["length",5],["элемента",5],["formmodel",5],["from",5],["core",5],["value",5],["component",5],["модели",4],["схеме",4],["массив",4],["import",4],["inputfield",4],["kit",4],["per",4],["formarraysection",4],["index",4],["map",4],["div",4],["button",4],["cross",4],["names",4],["modelarray",3],["removeat",3],["path",3],["его",3],["string",3],["схема",3],["field",3],["внутри",3],["definevalidationschema",3],["модель",3],["cdk",3],["formarray",3],["root",3],["массива",3],["формы",3],["плоские",3],["значения",3],["clear",3],["hasitems",3],["это",2],["itemmodel",2],["subschema",2],["одного",2],["createmodel",2],["number",2],["сигнал",2],["itemschema",2],["componentprops",2],["три",2],["только",2],["layout",2],["форма",2],["схемы",2],["для",2],["правила",2],["оператором",2],["validate",2],["required",2],["или",2],["itemcomponent",2],["валидация",2],["insertat",2],["при",2],["behavior",2],["выше",2],["formproxy",2],["usearraylength",2],["return",2],["formfield",2],["onclick",2],["remove",2],["add",2],["find_recipe",2],["topic",2],["sig",2],["поле",2],["носитель",2],["api",2],["signatures",2]]},{"id":"async-watchfield","file":"11-async-watchfield.md","title":"async-watchfield","purpose":"Для async-реакции на изменение поля (загрузка зависимых опций, справочников) используй `onChange` из `@reformer/core/behaviors`. Колбэк выполняется ВНЕ effect-контекста (можно безопасно писать сигналы/ноды), а 2-й аргумент — `{ signal }` (AbortSignal): при следующей смене значения предыдущий вызов аннулируется — передавай `signal` в `fetch`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"10. ASYNC REACTION — onChange (CRITICALLY IMPORTANT)","slug":"async-reaction-onchange-critically-important"}],"terms":[["onchange",7],["async",4],["reformer",4],["core",4],["signal",4],["model",4],["form",4],["watchfield",4],["при",3],["const",3],["value",3],["dependentfield",3],["updatecomponentprops",3],["options",3],["error",3],["debounce",3],["для",2],["изменение",2],["поля",2],["behaviors",2],["колбэк",2],["abortsignal",2],["fetch",2],["typescript",2],["import",2],["defineformbehavior",2],["from",2],["return",2],["try",2],["data",2],["catch",2],["aborterror",2],["умолчанию",2],["stop",2],["country",2]]},{"id":"array-cleanup","file":"12-array-cleanup.md","title":"array-cleanup","purpose":"Очистка массива при выключении флага — через `onChange` на сигнале флага + `clear()` на модели-массиве. Колбит выполняется вне effect-контекста, поэтому мутировать массив безопасно.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"11. ARRAY CLEANUP PATTERN","slug":"array-cleanup-pattern"}],"terms":[["onchange",5],["model",5],["clear",4],["array",3],["флага",3],["import",3],["from",3],["reformer",3],["core",3],["behaviors",3],["hasitems",3],["при",2],["массив",2],["typescript",2],["defineformbehavior",2],["readonlysignal",2],["clearwhenoff",2],["flag",2],["void",2]]},{"id":"multi-step","file":"13-multi-step.md","title":"multi-step","purpose":"Каждый шаг — своя `ValidationSchema<Form>` (обычная функция `({ model }) => void`, обёрнутая `defineValidationSchema`). Переход к следующему шагу проверяется `validateModel(model, stepSchema)`; полный submit — по общей схеме (композиция шагов через `apply(...)`). `validateModel` сам разносит ошибки по нодам формы (`getNodeForSignal(sig).setErrors(...)`), поэтому UI подсветит проблемные поля текущего шага автоматически. Валидация живёт ОТДЕЛЬНО от layout: сам `RenderNode`/JSON-узел валидаторов не несёт — схема инъектируется в рантайме и роутит ошибки по тем же нодам.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"12. MULTI-STEP FORM VALIDATION","slug":"multi-step-form-validation"}],"terms":[["model",17],["step",12],["form",11],["const",11],["validatemodel",9],["validate",6],["definevalidationschema",5],["required",5],["step_schemas",5],["apply",4],["await",4],["button",4],["validationschema",3],["submit",3],["ошибки",3],["шага",3],["схема",3],["typescript",3],["import",3],["from",3],["reformer",3],["core",3],["fullschema",3],["promise",3],["boolean",3],["async",3],["return",3],["setstep",3],["multi",2],["validation",2],["обычная",2],["функция",2],["void",2],["переход",2],["следующему",2],["шагу",2],["полный",2],["общей",2],["схеме",2],["композиция",2],["шагов",2],["через",2],["сам",2],["нодам",2],["sig",2],["текущего",2],["автоматически",2],["type",2],["formmodel",2],["min",2],["под",2],["step1schema",2],["step2schema",2],["personaldata",2],["currentstep",2],["handlesubmit",2],["function",2],["nextstep",2],["div",2],["onclick",2]]},{"id":"extended-mistakes","file":"14-extended-mistakes.md","title":"extended-mistakes","purpose":"// ✅ apply — одна под-схема на несколько групп const addressBehavior = defineFormBehavior<Address>(({ model }) => { compute(model.$.full, () => `${model.city}, ${model.street}`); });","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"13. EXTENDED COMMON MISTAKES","slug":"extended-common-mistakes"}],"terms":[["model",35],["compute",11],["cross",8],["reformer",6],["core",6],["const",6],["form",6],["field1",6],["apply",5],["typescript",5],["root",5],["applyeach",4],["import",4],["defineformbehavior",4],["from",4],["row",4],["field",4],["scope",4],["validate",4],["validatemodel",4],["value",4],["transformvalue",3],["цикл",3],["prefix",3],["firstname",3],["cycle",3],["detected",3],["валидации",3],["validation",3],["правило",3],["ошибки",3],["validators",3],["validateformmodel",3],["field2",3],["null",3],["schema",3],["под",2],["схема",2],["addressbehavior",2],["qty",2],["price",2],["setvalue",2],["guard",2],["ручной",2],["onchange",2],["запись",2],["fullname",2],["lastname",2],["peek",2],["без",2],["стабилизации",2],["поля",2],["или",2],["через",2],["валидация",2],["sig",2],["поле",2],["читает",2],["get",2],["definevalidationschema",2],["required",2],["revalidatewhen",2],["validationerror",2],["modelvalidator",2],["legacyrule",2],["code",2],["error",2],["message",2],["invalid",2],["legacyschema",2],["field1lessthanfield2",2],["только",2],["api",2],["ctx",2]]},{"id":"project-structure","file":"15-project-structure.md","title":"project-structure","purpose":"One form is one module folder. **Default layout — flat minimalist:** every concern is a single file at the module root, and the whole form (entry + all wizard steps inline) lives in one `index.tsx`. Filenames are flat with no prefix — **the dot-prefix (`form.` / `renderer.`) is carried only by `schema` and `behavior`**, the two concerns that have both a model-layer and a render-layer version (`form.schema.ts` / `form.behavior.ts` here; renderer slices add `renderer.schema.*` / `renderer.behavior.ts`). No `lib/` / `schema/` / `components/steps/` nesting until the form grows (see \"Scaling up\" below). Arrays are declared in `form.schema.ts` and rendered with `FormArraySection` — no per-step component files.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"14. PROJECT STRUCTURE (COLOCATION)","slug":"project-structure-colocation"}],"terms":[["form",35],["the",28],["model",24],["schema",22],["behavior",14],["forms",14],["one",12],["steps",11],["renderer",11],["validation",11],["module",10],["index",10],["tsx",9],["component",9],["layout",7],["file",7],["and",7],["entry",7],["components",7],["step",7],["all",6],["with",6],["type",6],["folders",6],["flat",5],["root",5],["lib",5],["per",5],["value",5],["credit",5],["application",5],["export",5],["creditapplicationform",5],["loantype",5],["reformer",5],["folder",4],["default",4],["minimalist",4],["concern",4],["inline",4],["reusable",4],["createcoreform",4],["types",4],["createmodel",4],["defineformbehavior",4],["for",4],["import",4],["formmodel",4],["from",4],["core",4],["const",4],["structure",3],["single",3],["whole",3],["wizard",3],["only",3],["scaling",3],["formarraysection",3],["files",3],["formwizard",3],["option",3],["array",3],["factories",3],["compute",3],["enablewhen",3],["data",3],["sources",3],["api",3],["tree",3],["when",3],["personaldata",3],["large",3],["domain",3],["react",3],["every",2],["lives",2],["are",2],["prefix",2],["concerns",2],["that",2],["layer",2],["see",2],["below",2],["arrays",2],["name",2],["call",2],["enums",2],["label",2],["dictionaries",2],["initial",2],["values",2],["element",2],["componentprops",2],["validatestep",2],["validateall",2],["submit",2],["rule",2],["thumb",2],["this",2],["createcreditapplicationmodel",2],["creditapplicationschema",2],["properties",2],["creditapplicationbehavior",2],["monthlypayment",2],["renders",2],["createcreditapplicationform",2],["builder",2],["you",2],["sub",2],["calc",2],["validators",2],["raw",2],["material",2],["definition",2],["plus",2],["target",2],["nested",2],["cross",2],["guide",2],["mcp",2],["variants",2],["json",2],["directory",2]]},{"id":"ui-components","file":"16-ui-components.md","title":"ui-components","purpose":"type RegistrationForm = { email: string; country: string; agree: boolean; };","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[{"note":"❌ **Свои field-компоненты с label-prop'ами в JSX**:\n\n```tsx\n// WRONG — дублирует логику FormField, ломает schema-driven архитектуру\n<Input control={form.email} label=\"Email\" placeholder=\"...\" />\n<Select control={form.country} options={[...]} />\n```\n\n❌ **Передача компонент-пропсов через JSX вместо схемы**:\n\n```tsx\n// WRONG — нарушает single source of truth (схема)\n<FormField control={form.email} label=\"Email\" />\n```\n\n✅ Всё это в схеме:\n\n```ts\n{ email: { component: InputField, componentProps: { label: 'Email' } } }\n```\n\n```tsx\n<FormField control={form.email} />\n```"}],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"14.5 UI COMPONENT PATTERNS","slug":"ui-component-patterns"}],"terms":[["label",24],["control",22],["formfield",16],["componentprops",16],["email",16],["form",15],["value",15],["reformer",11],["input",11],["string",11],["type",10],["from",10],["kit",9],["import",9],["placeholder",8],["tsx",8],["country",8],["agree",7],["const",7],["component",6],["useformcontrol",6],["errors",6],["disabled",6],["для",5],["schema",5],["options",5],["jsx",5],["model",5],["testid",5],["через",5],["обёртки",4],["если",4],["inputfield",4],["без",4],["return",4],["span",4],["свои",3],["field",3],["driven",3],["компонент",3],["как",3],["select",3],["etc",3],["props",3],["react",3],["core",3],["button",3],["registrationform",3],["function",3],["компоненты",3],["prop",3],["onchange",3],["fieldnode",3],["один",3],["myformfield",3],["unknown",3],["patterns",2],["default",2],["используй",2],["пиши",2],["design",2],["system",2],["особый",2],["подход",2],["схеме",2],["integration",2],["usememo",2],["createmodel",2],["createform",2],["selectfield",2],["checkboxfield",2],["сам",2],["всё",2],["wrong",2],["вместо",2],["схемы",2],["advanced",2],["children",2],["inputmask",2],["mask",2],["phone",2],["onblur",2],["myformfieldprops",2],["shouldshowerror",2],["record",2],["text",2],["setvalue",2],["target",2],["message",2],["рендерер",2],["renderer",2],["components",2],["div",2],["classname",2]]},{"id":"nonexistent-api","file":"17-nonexistent-api.md","title":"nonexistent-api","purpose":"**Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры, удалено при переходе на M1 и при разделении валидации/поведения):","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"15. NON-EXISTENT API (DO NOT USE)","slug":"non-existent-api-do-not-use"}],"terms":[["model",21],["reformer",13],["core",13],["value",12],["form",11],["нет",10],["import",10],["validators",9],["from",9],["validate",8],["validatemodel",8],["email",8],["definevalidationschema",6],["layout",6],["валидации",5],["cross",5],["validation",5],["correct",5],["schema",5],["field",5],["name",5],["type",5],["validatewhen",4],["each",4],["раннер",4],["wrong",4],["submit",4],["behaviors",4],["typescript",4],["поведение",4],["api",3],["path",3],["при",3],["ambient",3],["схема",3],["useform",3],["boolean",3],["схему",3],["существует",3],["component",3],["common",3],["required",3],["revalidatewhen",3],["const",3],["операторы",2],["validateasync",2],["apply",2],["живут",2],["createmodel",2],["createform",2],["хука",2],["validateform",2],["validateformmodel",2],["внешний",2],["await",2],["прогон",2],["applywhen",2],["валидация",2],["оператором",2],["validationschemafn",2],["validationschema",2],["defineformbehavior",2],["equalto",2],["rule",2],["sig",2],["запускают",2],["fieldpath",2],["пути",2],["ctx",2],["compute",2],["transformers",2],["useformcontrolvalue",2],["useformcontrol",2],["отдельный",2],["mistakes",2],["поле",2],["сигналу",2],["правила",2],["литерал",2],["модели",2],["inputfield",2],["componentprops",2],["label",2],["enablewhen",2],["city",2],["country",2]]},{"id":"conditional-fields","file":"18-conditional-fields.md","title":"Условные поля — видимость, доступность и валидация","purpose":"«Показать поле X только если Y» под M1 решается тремя независимыми механизмами — выбирай по тому, что именно должно быть условным:","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[{"note":"Условную валидацию раньше собирали иначе; эти формы удалены при переходе на `@reformer/core/validation`.\nНиже — **как НЕ надо** (чтобы не регрессировать); рабочий вариант — `validateWhen` из раздела выше.\n\n- **Нет branch-узла `{ when, children }` в дереве схемы.** Старая схема была деревом\n `{ value, validators: [...] }` с ветками `{ when: (scope, root) => boolean, children: [...] }`,\n которое обходил `validateFormModel`. Теперь схема — функция `({ model }) => void`, а условие —\n оператор `validateWhen(() => cond, () => {…})`; отдельного объекта-узла нет.\n\n ```typescript\n // УДАЛЕНО — так больше нельзя\n const schema = {\n children: [\n { value: model.$.loanType, validators: [required()] },\n { when: (_s, root) => root.loanType === 'mortgage', children: [ /* … */ ] },\n ],\n };\n await validateFormModel(model, schema); // символа нет\n ```\n\n- **`applyWhen` не существует** — ни как экспорт, ни как локальный сахар: он эмитил branch-узел,\n которого больше нет. Оборачивай условные правила оператором `validateWhen`.\n\n- **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationSchema<T>`\n (`(ctx: { model: FormModel<T> }) => void`), фабрика-обёртка — `defineValidationSchema<T>(fn)`.\n\n- **Cross-field — обычная функция над снапшотом, а не `ModelValidator (value, scope, root)`.**\n Соседние поля читаются из снимка модели (`f: Root`, эквивалент `model.get()`), а правило навешивается\n оператором `cross(sig, fn)`. Каноничное использование — `complex-multy-step-form/schemas/validation.ts`.\n\n ```typescript\n import { cross } from '@reformer/core/validation';\n import type { ValidationError } from '@reformer/core';\n\n // (f: Root) => error — сравнение с соседним полем, без scope/root-параметров\n const passwordsMatch = (f: { password: string; confirmPassword: string }): ValidationError | null =>\n f.confirmPassword && f.password && f.confirmPassword !== f.password\n ? { code: 'mismatch', message: 'Пароли не совпадают' }\n : null;\n\n // внутри defineValidationSchema — fn получает снапшот текущего scope (model.get()):\n cross(model.$.confirmPassword, passwordsMatch);\n ```"}],"examples":[],"troubleshooting":[],"seeAlso":["[03-api-signatures.md](./03-api-signatures.md) — сигнатуры `enableWhen`/`compute`/`copyFrom` (поведение) и `validate`/`validateWhen`/`cross`/`validateModel` (валидация)","[17-nonexistent-api.md](./17-nonexistent-api.md) — полный список удалённого API (`applyWhen`, `ValidationSchemaFn`, `validateFormModel`, …)","[25-reset-when.md](./25-reset-when.md) — `resetWhen` как альтернатива, когда поле остаётся enabled","[19-reading-values.md](./19-reading-values.md) — `useFormControlValue` и чтение значений в React"],"sections":[{"heading":"Purpose","slug":"purpose"},{"heading":"Видимость и доступность","slug":"vidimost-i-dostupnost"},{"heading":"Условная валидация","slug":"uslovnaya-validatsiya"},{"heading":"Скрытие в JSX","slug":"skrytie-v-jsx"},{"heading":"Anti-patterns удалённого контракта валидации","slug":"anti-patterns-udalennogo-kontrakta-validatsii"},{"heading":"See also","slug":"see-also-2"}],"terms":[["model",31],["enablewhen",12],["validatewhen",12],["when",11],["reformer",11],["core",11],["loantype",11],["только",9],["form",9],["поля",7],["при",7],["mortgage",7],["root",7],["валидация",6],["поле",6],["compute",6],["copyfrom",6],["definevalidationschema",6],["нет",6],["api",6],["import",6],["from",6],["null",6],["const",6],["schema",6],["validate",6],["cross",6],["required",6],["схемы",5],["validatemodel",5],["useformcontrolvalue",5],["validation",5],["children",5],["валидации",5],["resetondisable",5],["propertyvalue",5],["initialpayment",5],["доступность",4],["behaviors",4],["условная",4],["правила",4],["cond",4],["удалённого",4],["true",4],["значение",4],["typescript",4],["creditform",4],["boolean",4],["string",4],["field",4],["value",4],["как",4],["scope",4],["confirmpassword",4],["видимость",3],["под",3],["быть",3],["модели",3],["для",3],["значения",3],["оператор",3],["внутри",3],["скрытие",3],["jsx",3],["branch",3],["узла",3],["applywhen",3],["больше",3],["anti",3],["patterns",3],["контракта",3],["nonexistent",3],["условие",3],["без",3],["оператором",3],["validators",3],["тип",3],["чтобы",3],["return",3],["validateformmodel",3],["password",3],["условные",2],["если",2],["что",2],["именно",2],["должно",2],["остаётся",2],["раннером",2],["узел",2],["вообще",2],["условный",2],["реактивно",2],["его",2],["иначе",2],["сохраняется",2],["defineformbehavior",2],["type",2],["number",2],["sameemail",2],["email",2],["emailadditional",2],["или",2],["читает",2],["другого",2],["сигналы",2],["поведение",2],["через",2],["dsl",2],["cleanup",2],["сами",2],["когда",2],["group",2],["возвращает",2],["вариант",2],["условии",2],["ложном",2],["seterrors",2],["ошибки",2],["формы",2],["min",2],["ветка",2],["await",2],["ещё",2],["состояния",2],["раздела",2],["выше",2],["схема",2],["функция",2],["void",2],["validationschemafn",2],["get",2],["validationerror",2],["passwordsmatch",2],["signatures",2],["reset",2],["reading",2],["values",2]]},{"id":"reading-values","file":"19-reading-values.md","title":"reading-values","purpose":"Под M1 значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"16. READING FIELD VALUES (CRITICALLY IMPORTANT)","slug":"reading-field-values-critically-important"}],"terms":[["model",32],["address",12],["value",8],["email",8],["subscribe",6],["модели",5],["поля",5],["const",5],["city",5],["сигнал",5],["compute",5],["onchange",5],["значения",4],["сигналы",4],["typescript",4],["поле",4],["peek",4],["снимок",4],["field",3],["под",3],["доступ",3],["react",3],["control",3],["только",3],["модель",3],["внутри",3],["computed",3],["запись",3],["через",3],["нереактивный",3],["get",3],["для",3],["узел",3],["группы",3],["all",3],["addr",3],["amount",3],["term",3],["есть",2],["чтения",2],["хуки",2],["объект",2],["значение",2],["напрямую",2],["items",2],["effect",2],["читают",2],["реактивного",2],["дерева",2],["подписка",2],["целиком",2],["readonlysignal",2],["поэтому",2],["behaviors",2],["читаем",2],["defineformbehavior",2],["form",2]]},{"id":"compute-vs-watch","file":"20-compute-vs-watch.md","title":"compute-vs-watch","purpose":"Под M1 behaviors работают на сигналах модели. Есть два способа их писать:","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"17. COMPUTE vs ONCHANGE","slug":"compute-vs-onchange"}],"terms":[["model",36],["compute",16],["map",10],["onchange",9],["reformer",9],["core",9],["watchfield",8],["computefrom",7],["typescript",7],["только",6],["behaviors",5],["defineformbehavior",5],["примитив",5],["читай",5],["value",5],["proxy",5],["без",5],["cleanup",4],["для",4],["изменение",4],["async",4],["или",4],["import",4],["from",4],["const",4],["через",4],["array",4],["значения",4],["реакция",4],["debounce",4],["signal",4],["country",4],["сигналы",3],["dsl",3],["behavior",3],["target",3],["внутри",3],["поля",3],["пересчёт",3],["get",3],["field",3],["массива",3],["coborrowers",3],["элементов",3],["полей",3],["interestrate",3],["abortsignal",3],["поле",3],["любое",3],["работают",2],["модели",2],["есть",2],["писать",2],["массив",2],["side",2],["обновление",2],["componentprops",2],["auto",2],["tracking",2],["read",2],["цель",2],["нет",2],["peek",2],["myform",2],["level",2],["nested",2],["personaldata",2],["when",2],["это",2],["нереактивный",2],["зависимость",2],["отследится",2],["вне",2],["контекста",2],["сумма",2],["реактивно",2],["reduce",2],["читает",2],["трекает",2],["как",2],["length",2],["если",2],["нужен",2],["null",2],["явный",2],["источников",2],["monthlypayment",2],["amount",2],["term",2],["rate",2],["при",2],["form",2],["cities",2],["city",2],["группы",2],["address",2],["addr",2],["all",2],["формы",2],["производное",2]]},{"id":"array-operations","file":"21-array-operations.md","title":"array-operations","purpose":"Массивы объектов — model-owned. Мутации делаются через `ModelArray` (`model.arrayField`); рендер — через ноду формы (`form.items.map` / `.at`).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"18. ARRAY OPERATIONS","slug":"array-operations"}],"terms":[["model",22],["items",21],["item",17],["form",9],["array",7],["reformer",7],["name",6],["price",6],["core",6],["const",6],["typescript",5],["index",5],["import",5],["from",5],["row",5],["через",4],["map",4],["length",4],["myform",4],["return",4],["div",4],["button",4],["applyeach",4],["rows",4],["cross",4],["рендер",3],["формы",3],["formproxy",3],["push",3],["control",3],["remove",3],["add",3],["per",3],["defineformbehavior",3],["aggregateinto",3],["validation",3],["sum",3],["объектов",2],["modelarray",2],["ноду",2],["formmodel",2],["undefined",2],["плоские",2],["значения",2],["индексу",2],["removeat",2],["fieldconfig",2],["value",2],["component",2],["object",2],["usearraylength",2],["formfield",2],["onclick",2],["behavior",2],["compute",2],["behaviors",2],["others",2],["reduce",2],["percent",2],["sig",2],["скалярное",2],["поле",2],["носитель",2],["validationerror",2],["percentagessumto100",2],["null",2],["total",2]]},{"id":"cycle-detection","file":"22-cycle-detection.md","title":"Cycle Detection — предотвращение «Cycle detected»","purpose":"Behaviors на сигналах уже защищены от типичных циклов:","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"Как устроено под M1","slug":"kak-ustroeno-pod-m1"},{"heading":"Когда всё-таки возникает «Cycle detected»","slug":"kogda-vse-taki-voznikaet-cycle-detected"},{"heading":"Prefer built-in operators over manual logic","slug":"prefer-built-in-operators-over-manual-logic"},{"heading":"Key Rules","slug":"key-rules"}],"terms":[["model",24],["compute",8],["enablewhen",8],["условие",7],["transformvalue",6],["typescript",6],["цель",5],["resetwhen",5],["prefix",5],["resetondisable",5],["cycle",4],["guard",4],["cardnumber",4],["detected",3],["если",3],["onchange",3],["или",3],["значения",3],["поля",3],["when",3],["true",3],["уже",2],["computefrom",2],["только",2],["значение",2],["peek",2],["запись",2],["вне",2],["effect",2],["контекста",2],["читает",2],["пишет",2],["можно",2],["перед",2],["disable",2],["это",2],["расходящийся",2],["взаимный",2],["условием",2],["field",2],["copyfrom",2],["собственную",2],["зависит",2],["через",2],["insurancetype",2],["casco",2]]},{"id":"copy-from","file":"23-copy-from.md","title":"copyFrom — Копирование значений между полями","purpose":"`copyFrom` декларативно копирует значение одного поля (или группы) в другое при выполнении условия `when`. Используется для UX-сценариев «совпадает с …»: «адрес проживания = адрес регистрации», «email для уведомлений = основной email», «billing = shipping». Оператор сам выходит из reactive-контекста (`runOutsideEffect`), поэтому не порождает «Cycle detected».","keyConcepts":[],"api":"Есть две формы. **Примитив из `@reformer/core`** (сигнал → сигнал):\n\n```typescript\nfunction copyFrom<T>(\n source: ReadonlySignal<T>,\n target: Signal<T>,\n options?: { when?: () => boolean; transform?: (value: T) => T }\n): () => void; // возвращает cleanup\n```\n\n**DSL-оператор из `@reformer/core/behaviors`** (сигнал ИЛИ группа):\n\n```typescript\nfunction copyFrom<T>(\n source: ReadonlySignal<T> | object, // model.$.field или model.$.group\n target: Signal<T> | object,\n options?: { when?: () => boolean; transform?: (value: T) => T }\n): void; // cleanup управляется формой\n```\n\n`when` — реактивное условие (читает сигналы модели через `model.*`). `transform` применяется\nк значению перед записью в target. Отдельной опции `fields`/`debounce` нет.","patterns":[],"antiPatterns":[{"why":"Двусторонняя связь через два copyFrom — конфликт направлений","bad":"copyFrom(model.$.a, model.$.b);\ncopyFrom(model.$.b, model.$.a);","correctNote":"Двусторонняя синхронизация — это работа syncFields","correct":"syncFields(model.$.a, model.$.b);"},{"why":"when, читающий саму target — лишний триггер при перезаписи target","bad":"copyFrom(model.$.source, model.$.target, { when: () => model.target === '' });","correctNote":"when опирается на независимый флаг","correct":"copyFrom(model.$.source, model.$.target, { when: () => model.copyEnabled === true });"}],"examples":[],"troubleshooting":[],"seeAlso":["[24-sync-fields.md](./24-sync-fields.md) — двусторонняя синхронизация","[25-reset-when.md](./25-reset-when.md) — сброс target при выключенном условии","[20-compute-vs-watch.md](./20-compute-vs-watch.md) — `compute` для производных значений","[22-cycle-detection.md](./22-cycle-detection.md) — почему `runOutsideEffect` защищает от циклов"],"sections":[{"heading":"Purpose","slug":"purpose-2"},{"heading":"API","slug":"api"},{"heading":"Examples","slug":"examples"},{"heading":"Anti-patterns","slug":"anti-patterns"},{"heading":"Troubleshooting","slug":"troubleshooting-2"},{"heading":"See also","slug":"see-also-3"}],"terms":[["model",36],["copyfrom",20],["when",20],["target",14],["typescript",8],["при",7],["source",7],["defineformbehavior",7],["reformer",6],["core",6],["value",6],["true",6],["email",5],["boolean",5],["transform",5],["string",5],["значение",4],["или",4],["группы",4],["для",4],["cycle",4],["behaviors",4],["import",4],["from",4],["const",4],["примитив",3],["сигнал",3],["fields",3],["синхронизация",3],["type",3],["useshippingasbilling",3],["shippingaddress",3],["billingaddress",3],["export",3],["registrationaddress",3],["sameemail",3],["emailadditional",3],["как",3],["двусторонняя",3],["reset",3],["compute",3],["копирование",2],["значений",2],["адрес",2],["оператор",2],["runoutsideeffect",2],["detected",2],["есть",2],["function",2],["readonlysignal",2],["signal",2],["options",2],["void",2],["возвращает",2],["cleanup",2],["object",2],["условие",2],["читает",2],["через",2],["orderform",2],["profileform",2],["sameasregistration",2],["address",2],["residenceaddress",2],["копировании",2],["contactform",2],["cleanups",2],["anti",2],["syncfields",2],["copyenabled",2],["behavior",2],["пишет",2],["sync",2],["watch",2],["detection",2]]},{"id":"sync-fields","file":"24-sync-fields.md","title":"syncFields — Двусторонняя синхронизация полей","purpose":"`syncFields` создаёт двунаправленную связь между двумя полями: изменение любого из них переписывает второе. Применяется для дублей одного значения в разных частях формы (тех. поле + видимое представление, mirror-поля). Внутренний флаг + `runOutsideEffect` исключают петли. Для одностороннего копирования — [`copyFrom`](./23-copy-from.md); для расчётов — [`compute`](./20-compute-vs-watch.md).","keyConcepts":[],"api":"Одинаково в примитиве (`@reformer/core`) и DSL (`@reformer/core/behaviors`):\n\n```typescript\n// примитив: возвращает cleanup\nfunction syncFields<T>(a: Signal<T>, b: Signal<T>, options?: { transform?: (value: T) => T }): () => void;\n\n// DSL: cleanup управляется формой\nfunction syncFields<T>(a: Signal<T>, b: Signal<T>, options?: { transform?: (value: T) => T }): void;\n```\n\n`a`/`b` — сигналы (`model.$.field`) совместимого типа `T`. `transform` **асимметричен**:\nприменяется только при движении значения `a → b`. Опции `debounce`/`when` нет.","patterns":[],"antiPatterns":[{"why":"Симметрично через два copyFrom — конфликт направлений","bad":"copyFrom(model.$.a, model.$.b);\ncopyFrom(model.$.b, model.$.a);","correctNote":"syncFields умеет двустороннюю связь без петель","correct":"syncFields(model.$.a, model.$.b);"},{"why":"Ожидание, что transform применится в обе стороны","bad":"syncFields(model.$.a, model.$.b, { transform: (v) => v.trim() });\n// при записи в b значение НЕ trim-ается","correctNote":"Симметричные трансформы — syncFields + transformValue на обоих полях","correct":"syncFields(model.$.a, model.$.b);\ntransformValue(model.$.a, (v) => (typeof v === 'string' ? v.trim() : v));\ntransformValue(model.$.b, (v) => (typeof v === 'string' ? v.trim() : v));"},{"why":"Поля разного типа — рантайм-приведение и баги","bad":"syncFields(model.$.amountString, model.$.amountNumber); // string ↔ number","correctNote":"Для конвертации — compute в обе стороны или один канонический формат + computed отображение","correct":"compute(model.$.amountNumber, () => Number(model.amountString));"}],"examples":[],"troubleshooting":[],"seeAlso":["[23-copy-from.md](./23-copy-from.md) — однонаправленное копирование с `when`","[26-transform-value.md](./26-transform-value.md) — нормализация значений на месте","[22-cycle-detection.md](./22-cycle-detection.md) — почему симметричный copy ломается","[20-compute-vs-watch.md](./20-compute-vs-watch.md) — `compute` для производных значений"],"sections":[{"heading":"Purpose","slug":"purpose-3"},{"heading":"API","slug":"api-2"},{"heading":"Examples","slug":"examples-2"},{"heading":"Anti-patterns","slug":"anti-patterns-2"},{"heading":"Troubleshooting","slug":"troubleshooting-3"},{"heading":"See also","slug":"see-also-4"}],"terms":[["model",27],["syncfields",20],["transform",9],["compute",8],["value",8],["string",8],["copyfrom",7],["typescript",7],["from",6],["when",6],["для",5],["reformer",5],["core",5],["defineformbehavior",5],["поля",4],["copy",4],["signal",4],["при",4],["syncfield1",4],["syncfield2",4],["как",4],["trim",4],["transformvalue",4],["watch",3],["behaviors",3],["import",3],["const",3],["internalcode",3],["displaycode",3],["typeof",3],["через",3],["cycle",3],["полей",2],["связь",2],["применяется",2],["значения",2],["dsl",2],["примитив",2],["cleanup",2],["function",2],["options",2],["void",2],["типа",2],["только",2],["нет",2],["type",2],["mirrorform",2],["export",2],["нормализация",2],["записи",2],["displayform",2],["значение",2],["stop",2],["два",2],["что",2],["обе",2],["стороны",2],["amountstring",2],["amountnumber",2],["number",2],["условием",2],["значений",2],["detection",2]]},{"id":"reset-when","file":"25-reset-when.md","title":"resetWhen — Условный сброс полей","purpose":"`resetWhen` сбрасывает значение поля к `resetValue`, когда `condition` истинно. Это альтернатива `enableWhen({ resetOnDisable: true })`, когда поле остаётся **enabled**, но содержимое нужно очистить (например, переключение способа оплаты обнуляет «номер карты», но поле по-прежнему доступно). По умолчанию пишет `null`; `resetValue` задаёт произвольное значение.","keyConcepts":[],"api":"Одинаково в примитиве (`@reformer/core`) и DSL (`@reformer/core/behaviors`):\n\n```typescript\n// примитив: возвращает cleanup\nfunction resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: { resetValue?: T }): () => void;\n\n// DSL: cleanup управляется формой\nfunction resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: { resetValue?: T }): void;\n```\n\n`target` — сигнал (`model.$.field`). `condition` — реактивное условие (читает `model.*`).\n`resetValue` по умолчанию `null`. Опций `onlyIfDirty`/`debounce` нет; флаги dirty/touched\nполя не трогаются оператором (значения принадлежат модели).","patterns":[],"antiPatterns":[{"why":"resetWhen вместо enableWhen для disable-сценария","bad":"resetWhen(model.$.field, () => !model.show);\n// поле останется enabled и валидируемым — ошибка required всё равно прилетит","correctNote":"Если поле должно «исчезнуть» — блокируем + сбрасываем","correct":"enableWhen(model.$.field, () => model.show, { resetOnDisable: true });"},{"why":"resetValue с типом, отличным от поля","bad":"resetWhen(model.$.amount, () => model.skipPayment, { resetValue: 'none' }); // amount: number ← string","correctNote":"resetValue совместим с типом поля","correct":"resetWhen(model.$.amount, () => model.skipPayment, { resetValue: 0 });"},{"why":"Для строкового поля без resetValue прилетит null, а Input ждёт string","bad":"resetWhen(model.$.cardNumber, () => model.paymentType !== 'card');","correctNote":"Явный resetValue для строк","correct":"resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue: '' });"},{"why":"condition читает саму цель — самотриггер (см. 22-cycle-detection.md)","bad":"resetWhen(model.$.cardNumber, () => model.cardNumber !== '');","correctNote":"condition зависит только от независимого поля","correct":"resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue: '' });"}],"examples":[],"troubleshooting":[],"seeAlso":["[04-common-patterns.md](./04-common-patterns.md) — `enableWhen({ resetOnDisable: true })` как альтернатива","[23-copy-from.md](./23-copy-from.md) — копирование, у которого нет встроенного отката","[22-cycle-detection.md](./22-cycle-detection.md) — почему `condition` не должен читать целевое поле"],"sections":[{"heading":"Purpose","slug":"purpose-4"},{"heading":"API","slug":"api-3"},{"heading":"Examples","slug":"examples-3"},{"heading":"Anti-patterns","slug":"anti-patterns-3"},{"heading":"Troubleshooting","slug":"troubleshooting-4"},{"heading":"See also","slug":"see-also-5"}],"terms":[["model",31],["resetwhen",22],["resetvalue",18],["condition",10],["cardnumber",9],["поля",8],["typescript",8],["paymenttype",7],["card",7],["для",6],["enablewhen",5],["true",5],["поле",5],["reformer",5],["core",5],["defineformbehavior",5],["from",5],["resetondisable",4],["null",4],["сброс",3],["behaviors",3],["примитив",3],["target",3],["field",3],["читает",3],["import",3],["string",3],["const",3],["propertyvalue",3],["number",3],["initialpayment",3],["patterns",3],["amount",3],["cycle",3],["detection",3],["что",3],["полей",2],["значение",2],["когда",2],["альтернатива",2],["enabled",2],["способа",2],["оплаты",2],["карты",2],["умолчанию",2],["dsl",2],["возвращает",2],["cleanup",2],["function",2],["signal",2],["boolean",2],["options",2],["void",2],["нет",2],["значения",2],["type",2],["checkoutform",2],["export",2],["mortgageform",2],["без",2],["сбрасываем",2],["как",2],["show",2],["прилетит",2],["типом",2],["skippayment",2],["зависит",2],["behavior",2],["или",2],["сброса",2],["common",2],["copy",2]]},{"id":"transform-value","file":"26-transform-value.md","title":"transformValue — Автоматическая трансформация значений","purpose":"`transformValue` подписывается на изменение поля и переписывает его трансформированной версией: uppercase для кодов, trim+toLowerCase для email, округление для чисел. Применяется единообразно ко всем источникам изменения (пользователь, `model.field = …`, `model.set/patch`, `copyFrom`). **Идемпотентность (`f(f(x)) === f(x)`) обязательна** — иначе бесконечный цикл. Оператор откладывает запись вне effect-контекста (`runOutsideEffect`) и не пишет, если `transformer(value) === value` — базовый guard от циклов.","keyConcepts":[],"api":"Одинаково в примитиве (`@reformer/core`) и DSL (`@reformer/core/behaviors`):\n\n```typescript\n// примитив: возвращает cleanup\nfunction transformValue<T>(target: Signal<T>, transformer: (value: T) => T): () => void;\n\n// DSL: cleanup управляется формой\nfunction transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;\n```\n\n`target` — сигнал (`model.$.field`). `transformer` — чистая идемпотентная функция значения.\nДополнительных опций (`debounce`, `onUserChangeOnly`, `emitEvent`) и готового набора\n`transformers`/`createTransformer` НЕТ — трансформер пишется как обычная функция.","patterns":[],"antiPatterns":[{"why":"Неидемпотентный transformer — бесконечный цикл","bad":"transformValue(model.$.field, (v) => `prefix-${v}`); // f(f(x)) ≠ f(x)","correctNote":"Guard внутри transformer","correct":"transformValue(model.$.field, (v) => (v?.startsWith('prefix-') ? v : `prefix-${v}`));"},{"why":"transformValue ОДНОВРЕМЕННО с syncFields на том же поле","bad":"syncFields(model.$.a, model.$.b);\ntransformValue(model.$.b, (v) => v.toUpperCase()); // взаимные перезаписи","correctNote":"Трансформируй источник ДО синхронизации","correct":"transformValue(model.$.a, (v) => v.toUpperCase());\nsyncFields(model.$.a, model.$.b);"},{"why":"transformValue для производных полей (нет доступа к другим полям)","bad":"transformValue(model.$.fullName, () => `${model.firstName} ${model.lastName}`);","correctNote":"Для зависимостей от других полей — compute","correct":"compute(model.$.fullName, () => `${model.firstName} ${model.lastName}`);"}],"examples":[],"troubleshooting":[],"seeAlso":["[24-sync-fields.md](./24-sync-fields.md) — порядок применения с syncFields","[23-copy-from.md](./23-copy-from.md) — `transform`-опция при копировании","[22-cycle-detection.md](./22-cycle-detection.md) — про идемпотентность","[16-ui-components.md](./16-ui-components.md) — `<InputMask>` для тяжёлого форматирования"],"sections":[{"heading":"Purpose","slug":"purpose-5"},{"heading":"API","slug":"api-4"},{"heading":"Examples","slug":"examples-4"},{"heading":"Anti-patterns","slug":"anti-patterns-4"},{"heading":"Troubleshooting","slug":"troubleshooting-5"},{"heading":"See also","slug":"see-also-6"}],"terms":[["model",27],["transformvalue",24],["value",17],["transformer",10],["typescript",8],["для",7],["target",7],["trim",6],["reformer",6],["const",6],["touppercase",6],["digits",6],["core",5],["defineformbehavior",5],["from",5],["string",5],["email",4],["field",4],["signal",4],["slice",4],["syncfields",4],["behaviors",3],["примитив",3],["нет",3],["import",3],["uppercasefield",3],["return",3],["prefix",3],["cycle",3],["при",3],["трансформация",2],["uppercase",2],["tolowercase",2],["округление",2],["применяется",2],["идемпотентность",2],["бесконечный",2],["цикл",2],["вне",2],["базовый",2],["guard",2],["dsl",2],["cleanup",2],["function",2],["void",2],["функция",2],["готового",2],["набора",2],["как",2],["type",2],["promoform",2],["export",2],["contactform",2],["phone",2],["amount",2],["number",2],["телефон",2],["promocode",2],["toupper",2],["неидемпотентный",2],["полей",2],["fullname",2],["firstname",2],["lastname",2],["compute",2],["проверь",2],["что",2],["behavior",2],["inputmask",2],["sync",2],["fields",2],["copy",2],["detection",2],["components",2]]},{"id":"revalidate-when","file":"27-revalidate-when.md","title":"revalidateWhen — Перевалидация по триггерам","purpose":"`revalidateWhen` вызывает переданный колбэк ревалидации, когда изменяется любая из зависимостей. Применяется, когда правило зависит от значений других полей (`amount <= maxAmount`, `confirmPassword === password`, `initialPayment >= propertyValue * 0.2`). Валидация — отдельная функция-схема (`@reformer/core/validation`), прогоняемая по требованию через `validateModel(model, schema)`. Поэтому ревалидация выражается явным колбэком, а не автоматической привязкой к полю: поведение (`revalidateWhen`) лишь _инициирует_ прогон схемы валидации при изменении зависимости.","keyConcepts":[],"api":"`revalidateWhen` — оператор поведения (`@reformer/core/behaviors`, либо примитив `@reformer/core`).\nЕго сигнатура НЕ зависит от контракта валидации:\n\n```typescript\n// примитив: возвращает cleanup\nfunction revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): () => void;\n\n// DSL: cleanup управляется формой\nfunction revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;\n```\n\n`deps` — массив сигналов-триггеров (`model.$.field`). `revalidate` — колбэк (обычно\n`() => void validateModel(model, schema)`, где `schema` — `defineValidationSchema<T>(({ model }) => …)`).\nВызывается при изменении любой зависимости (НЕ на инициализации), вне effect-контекста.","patterns":[],"antiPatterns":[{"why":"Триггер == поле, которое и так меняется само","bad":"revalidateWhen([model.$.amount], () => validateModel(model, schema));\n// amount и так валидируется при собственном изменении в общем прогоне","correctNote":"Триггеры — ДРУГИЕ поля, от которых зависит правило amount","correct":"revalidateWhen([model.$.maxAmount, model.$.discount], () => validateModel(model, schema));"},{"why":"Правило только через revalidateWhen, но в схеме нет зависимости от триггера","bad":"revalidateWhen([model.$.maxAmount], () => validateModel(model, schema));\n// а валидатор amount — статичный max(1000), не читает maxAmount","correctNote":"Сначала cross-field правило (читает снапшот формы) в схеме валидации, потом revalidateWhen","correct":"const amountVsMax = (f: Form): ValidationError | null =>\n f.amount != null && f.maxAmount != null && f.amount > f.maxAmount\n ? { code: 'tooBig', message: '...' }\n : null;\n\nconst schema = defineValidationSchema<Form>(({ model }) => {\n cross(model.$.amount, amountVsMax); // навешиваем правило на поле amount\n});\nrevalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));"}],"examples":[],"troubleshooting":[],"seeAlso":["[03-api-signatures.md](./03-api-signatures.md) — cross-field `cross(sig, fn)` и раннер `validateModel`","[28-submit-and-reset.md](./28-submit-and-reset.md) — полная валидация формы","[20-compute-vs-watch.md](./20-compute-vs-watch.md) — реактивные производные значения"],"sections":[{"heading":"Purpose","slug":"purpose-6"},{"heading":"API","slug":"api-5"},{"heading":"Examples","slug":"examples-5"},{"heading":"Anti-patterns","slug":"anti-patterns-5"},{"heading":"Troubleshooting","slug":"troubleshooting-6"},{"heading":"See also","slug":"see-also-7"}],"terms":[["model",37],["revalidatewhen",21],["validatemodel",19],["amount",13],["maxamount",11],["reformer",11],["core",11],["void",11],["schema",10],["cross",10],["правило",8],["import",8],["from",8],["const",8],["typescript",7],["defineformbehavior",7],["validation",5],["при",5],["field",5],["где",5],["зависит",4],["через",4],["api",4],["behaviors",4],["deps",4],["amountvsmax",4],["читает",4],["формы",4],["только",4],["null",4],["перевалидация",3],["колбэк",3],["confirmpassword",3],["password",3],["propertyvalue",3],["схема",3],["валидации",3],["изменении",3],["зависимости",3],["примитив",3],["revalidate",3],["definevalidationschema",3],["export",3],["paymentschema",3],["схеме",3],["снапшот",3],["signatures",3],["схему",3],["поле",3],["триггера",3],["form",3],["sig",3],["вызывает",2],["когда",2],["полей",2],["initialpayment",2],["валидация",2],["функция",2],["поэтому",2],["cleanup",2],["function",2],["readonlysignal",2],["unknown",2],["триггеров",2],["вне",2],["смене",2],["для",2],["loanamount",2],["passwordsmatch",2],["как",2],["так",2],["поля",2],["ошибки",2],["onefieldschema",2],["раннер",2],["submit",2],["and",2],["reset",2],["compute",2],["watch",2]]},{"id":"submit-and-reset","file":"28-submit-and-reset.md","title":"Submit и Reset — Жизненный цикл отправки формы","purpose":"Канонический submit-флоу под M1: «запустить полную валидацию данных (`validateModel`) → проверить `boolean`-результат → достать снимок (`model.get()`) → сделать запрос → `model.reset()`». Валидация — **отдельный слой** (`@reformer/core/validation`, схема через `defineValidationSchema`), она НЕ входит в layout-дерево, которое получает `createForm`. Раннер `validateModel(model, schema)` сам роутит ошибки в ноды формы, поэтому UI подсветит проблемные поля, а наружу вернёт лишь `boolean` (false = есть блокирующая ошибка; `severity:'warning'` показывается, но submit не блокирует). `model.reset()` возвращает значения к initial-снимку.","keyConcepts":[],"api":"```typescript\n// Модель (источник истины значений):\ninterface ModelApi<T> {\n get(): T; // снимок значений — для submit\n set(value: T): void; // полная установка (все ключи T)\n patch(value: Partial<T>): void; // частичное слияние\n isDirty(): boolean; // отличаются ли значения от initial-снимка\n reset(): void; // вернуть к initial-снимку\n captureInitial(): void; // зафиксировать текущие как новый initial\n}\n\n// Валидация данных — headless-раннер: находит ноды по сигналам модели, роутит в них ошибки,\n// отменяет устаревшие прогоны той же (model, schema). Возвращает ТОЛЬКО boolean:\nvalidateModel<T>(model: FormModel<T>, schema: ValidationSchema<T>): Promise<boolean>\n// true = нет блокирующих ошибок (severity:'warning' не блокирует, но показывается).\n// false = есть блокирующая ошибка; она уже проставлена в ноду → UI подсветит поле.\n\n// Схема валидации — отдельный слой над моделью (не смешивается с layout):\ndefineValidationSchema<T>(({ model }) => {\n validate(model.$.field, [rules]); // синхронные правила поля\n // validateAsync / validateWhen / cross / each / apply — см. contract-spec\n}): ValidationSchema<T>\n\n// Нода поля/формы (для точечной работы с UI-состоянием):\nform.markAsTouched(); // тронуть все поля (показать ошибки)\nform.clearErrors(); // снять ошибки со всех нод\nform.<field>.setErrors([{ code, message }]); // серверная/бизнес-ошибка в конкретное поле\nform.<field>.clearErrors();\n```","patterns":[],"antiPatterns":[{"why":"Обращение к .valid: раннер возвращает boolean, а не { valid, errors }","bad":"const result = await validateModel(model, schema);\nif (!result.valid) return; // result — boolean, .valid === undefined → guard никогда не сработает","correctNote":"Результат — сам boolean","correct":"const valid = await validateModel(model, schema);\nif (!valid) return;"},{"why":"Чтение результата валидации без await — сабмитим по неполному вердикту","bad":"const handleSubmit = (e) => {\n e.preventDefault();\n validateModel(model, schema); // Promise проигнорирован, async-валидаторы не дождались\n submit(model.get());\n};","correctNote":"Дождаться и проверить boolean","correct":"const handleSubmit = async (e) => {\n e.preventDefault();\n if (await validateModel(model, schema)) submit(model.get());\n};"},{"why":"Валидаторы в layout-дереве, отдаваемом в createForm — layout не несёт правил","bad":"createForm({ model, schema: { username: { value: '', validators: [required()] } } });","correctNote":"Валидация — отдельная схема, прогоняется раннером","correct":"const schema = defineValidationSchema<T>(({ model }) => {\n validate(model.$.username, [required()]);\n});\nawait validateModel(model, schema);"},{"why":"model.reset() до ответа сервера — потеря данных при ошибке","bad":"await api.send(model.get());\nmodel.reset();","correctNote":"Reset только после успеха","correct":"try {\n await api.send(model.get());\n model.reset();\n} catch (err) { showError(err); }"},{"why":"Сброс полей по очереди — многословно","bad":"model.username = '';\nmodel.email = '';\nmodel.password = '';","correctNote":"model.reset() — к initial-снимку одним вызовом","correct":"model.reset();"}],"examples":[],"troubleshooting":[],"seeAlso":["[29-async-preload.md](./29-async-preload.md) — initial values и preload через `set`/`patch`","[13-multi-step.md](./13-multi-step.md) — пошаговая валидация через `validateModel` (`validateStep`/`validateAll`)","[27-revalidate-when.md](./27-revalidate-when.md) — мост «поведение → валидация»: `revalidateWhen([deps], () => void validateModel(model, schema))`","[03-api-signatures.md](./03-api-signatures.md) — сигнатуры модели и `validateModel`","[05-common-mistakes.md](./05-common-mistakes.md) — типичные ошибки"],"sections":[{"heading":"Purpose","slug":"purpose-7"},{"heading":"API","slug":"api-6"},{"heading":"Examples","slug":"examples-6"},{"heading":"Anti-patterns","slug":"anti-patterns-6"},{"heading":"Troubleshooting","slug":"troubleshooting-7"},{"heading":"See also","slug":"see-also-8"}],"terms":[["model",55],["validatemodel",21],["const",21],["reset",19],["form",16],["schema",14],["submit",12],["valid",12],["await",12],["get",11],["ошибки",11],["boolean",10],["return",10],["initial",9],["email",9],["username",9],["button",8],["валидация",7],["поля",7],["ошибка",7],["api",7],["typescript",7],["message",7],["password",7],["async",7],["данных",6],["reformer",6],["core",6],["layout",6],["createform",6],["возвращает",6],["значения",6],["validate",6],["import",6],["from",6],["required",6],["после",6],["definevalidationschema",5],["раннер",5],["void",5],["только",5],["true",5],["поле",5],["createmodel",5],["type",5],["handlesubmit",5],["response",5],["схема",4],["ноды",4],["severity",4],["warning",4],["снимку",4],["patch",4],["field",4],["clearerrors",4],["react",4],["registrationformdata",4],["чтобы",4],["function",4],["без",4],["preventdefault",4],["error",4],["формы",3],["validation",3],["через",3],["роутит",3],["подсветит",3],["false",3],["показывается",3],["блокирует",3],["set",3],["value",3],["модели",3],["валидации",3],["моделью",3],["markastouched",3],["всех",3],["tsx",3],["minlength",3],["string",3],["registrationschema",3],["шаг",3],["данные",3],["formfield",3],["control",3],["testid",3],["очистить",3],["result",3],["при",3],["preload",3],["проверить",2],["результат",2],["достать",2],["снимок",2],["сделать",2],["отдельный",2],["слой",2],["она",2],["входит",2],["дерево",2],["получает",2],["сам",2],["поэтому",2],["есть",2],["блокирующая",2],["модель",2],["значений",2],["для",2],["все",2],["partial",2],["isdirty",2],["captureinitial",2],["headless",2],["находит",2],["сигналам",2],["них",2],["устаревшие",2],["прогоны",2],["formmodel",2],["validationschema",2],["promise",2],["над",2],["показать",2],["нод",2],["seterrors",2],["code",2],["серверная",2],["бизнес",2],["usememo",2],["validators",2],["отдельная",2],["обязателен",2],["пароль",2],["formevent",2],["отправить",2],["payload",2],["register",2],["json",2],["успеха",2],["try",2],["форму",2],["catch",2],["handlereset",2],["clearjustpassword",2],["сброс",2],["onclick",2],["вне",2],["rawdata",2],["myform",2],["валидаторы",2],["сервера",2],["send",2],["err",2],["полей",2],["вернул",2],["загруженные",2],["multi",2],["step",2],["revalidate",2],["when",2],["signatures",2],["common",2],["mistakes",2]]},{"id":"async-preload","file":"29-async-preload.md","title":"Async Preload — Загрузка начальных значений и справочников","purpose":"Загрузка формы данными с сервера под M1: (1) **initial values** в `createModel(...)`; (2) **`model.set` / `model.patch`** для загрузки/обновления значений; (3) **external React-hook** для full-blown async preload (параллельный fetch заявки + справочников, обработка ошибок, race-guard через deps). Динамические `componentProps` (опции селектов) обновляются через `form.field.updateComponentProps({ options })` в `queueMicrotask`, чтобы не пересечься с реактивными эффектами от `set`/`patch`.","keyConcepts":[],"api":"```typescript\n// Модель:\nmodel.set(value: T): void; // полная установка значений (load полного DTO). Не меняет initial.\nmodel.patch(value: Partial<T>): void; // частичное слияние (только переданные ключи). Не меняет initial.\nmodel.reset(): void; // вернуть к initial-снимку\nmodel.captureInitial(): void; // сделать текущие значения новым initial-снимком\n\n// Нода поля/группы:\nform.field.updateComponentProps(props: Record<string, unknown>): void; // динамические опции и т.п.\n```\n\nInitial values задаются в `createModel(initial)`; `reset()` возвращает к ним. Чтобы загруженные\nданные стали новой «точкой отсчёта» для `reset()` — вызови `model.captureInitial()` после load.","patterns":[],"antiPatterns":[{"why":"model/form пересоздаются на каждый рендер → preload запускается каждый раз","bad":"function MyForm() {\n const model = createModel<T>(initial); // КАЖДЫЙ рендер!\n const form = createForm({ model, schema });\n}","correctNote":"Стабильные ссылки через useMemo","correct":"function MyForm() {\n const { model, form } = useMemo(() => {\n const m = createModel<T>(initial);\n return { model: m, form: createForm({ model: m, schema: buildSchema(m) }) };\n }, []);\n}"},{"why":"updateComponentProps синхронно после set → возможный конфликт с реактивными эффектами","bad":"model.set(data);\nform.region.updateComponentProps({ options: [...] });","correctNote":"queueMicrotask, чтобы реактивные эффекты от set завершились","correct":"model.set(data);\nqueueMicrotask(() => form.region.updateComponentProps({ options: [...] }));"},{"why":"Нет race-guard в async useEffect","bad":"useEffect(() => { fetchData(id).then((d) => model.set(d)); }, [id]);","correctNote":"Cleanup-флаг","correct":"useEffect(() => {\n let cancelled = false;\n fetchData(id).then((d) => { if (!cancelled) model.set(d); });\n return () => { cancelled = true; };\n}, [id]);"}],"examples":[],"troubleshooting":[],"seeAlso":["[28-submit-and-reset.md](./28-submit-and-reset.md) — обратная сторона жизненного цикла","[11-async-watchfield.md](./11-async-watchfield.md) — `onChange` для динамики после preload","[22-cycle-detection.md](./22-cycle-detection.md) — почему `queueMicrotask` нужен","[16-ui-components.md](./16-ui-components.md) — `updateComponentProps` для динамических опций"],"sections":[{"heading":"Purpose","slug":"purpose-8"},{"heading":"API","slug":"api-7"},{"heading":"Examples","slug":"examples-7"},{"heading":"Anti-patterns","slug":"anti-patterns-7"},{"heading":"Troubleshooting","slug":"troubleshooting-8"},{"heading":"See also","slug":"see-also-9"}],"terms":[["model",32],["form",17],["set",16],["initial",11],["updatecomponentprops",11],["const",11],["после",10],["через",9],["return",9],["async",8],["options",8],["reset",8],["error",8],["cancelled",8],["preload",7],["createmodel",7],["queuemicrotask",7],["value",7],["для",6],["typescript",6],["username",6],["isloading",6],["null",6],["applicationid",6],["region",6],["patch",5],["componentprops",5],["void",5],["import",5],["from",5],["reformer",5],["language",5],["marketing",5],["true",5],["schema",5],["useeffect",5],["setstate",5],["false",5],["onchange",5],["загрузка",4],["значений",4],["чтобы",4],["string",4],["createform",4],["core",4],["label",4],["data",4],["city",4],["каждый",4],["сервера",3],["values",3],["race",3],["guard",3],["динамические",3],["опции",3],["captureinitial",3],["возвращает",3],["вызови",3],["inputfield",3],["type",3],["component",3],["loadingstate",3],["function",3],["appresp",3],["dictsresp",3],["await",3],["cities",3],["err",3],["usememo",3],["справочников",2],["загрузки",2],["external",2],["react",2],["hook",2],["field",2],["реактивными",2],["эффектами",2],["модель",2],["load",2],["меняет",2],["снимку",2],["поля",2],["данные",2],["отсчёта",2],["selectfield",2],["profileform",2],["boolean",2],["usestate",2],["formmodel",2],["formproxy",2],["export",2],["creditapplicationform",2],["state",2],["let",2],["try",2],["status",2],["catch",2],["при",2],["defineformbehavior",2],["signal",2],["рендер",2],["myform",2],["fetchdata",2],["then",2],["submit",2],["and",2],["watchfield",2],["cycle",2],["detection",2],["components",2]]},{"id":"type-safety-recipes","file":"30-type-safety-recipes.md","title":"type-safety-recipes","purpose":"Идиоматичные паттерны, которые держат сгенерированный код без `any` и `as`-кастов под M1.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"30. TYPE-SAFETY RECIPES","slug":"type-safety-recipes"}],"terms":[["model",23],["type",16],["reformer",15],["core",15],["null",13],["recipe",10],["typescript",8],["import",8],["from",8],["value",8],["loanamount",7],["const",7],["behaviors",6],["validation",6],["compute",6],["string",5],["creditapplicationform",5],["field",5],["без",4],["типы",4],["схема",4],["definevalidationschema",4],["cross",4],["rule",4],["min",4],["поля",4],["personaldata",4],["полей",4],["для",4],["control",4],["fieldnode",4],["модель",3],["defineformbehavior",3],["validate",3],["form",3],["interface",3],["export",3],["propertyitem",3],["number",3],["properties",3],["модели",3],["правила",3],["validationerror",3],["initialpayment",3],["внутри",3],["напрямую",3],["root",2],["операторы",2],["validatemodel",2],["валидаторов",2],["validators",2],["createmodel",2],["formproxy",2],["modelsignals",2],["required",2],["max",2],["onchange",2],["примитив",2],["shape",2],["это",2],["array",2],["item",2],["выводятся",2],["валидаторы",2],["над",2],["снапшотом",2],["поле",2],["initialpaymentvsproperty",2],["propertyvalue",2],["code",2],["message",2],["читает",2],["касты",2],["minlength",2],["minage",2],["birthdate",2],["useformcontrol",2],["используй",2],["myfieldprops",2],["именованные",2],["validateadultage",2],["return",2],["age",2],["new",2],["date",2],["getfullyear",2],["myform",2]]},{"id":"async-validator-debounce","file":"31-async-validator-debounce.md","title":"async-validator-debounce","purpose":"Для проверок типа «уникальность email», «валидация ИНН через API», «проверка адреса» — async-правило это `AsyncRule<T>` = `(value, { signal }) => Promise<ValidationError | null>`. Оно регистрируется оператором `validateAsync(sig, [asyncRules])` внутри схемы валидации (`@reformer/core/validation`) и исполняется внешним раннером `validateModel` (async-правила прогоняются параллельно через `Promise.all`, раннер их дожидается).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":["[27-revalidate-when.md](27-revalidate-when.md) — перезапуск `validateModel` по триггерам","[29-async-preload.md](29-async-preload.md) — async preload данных при init формы","[32-async-options-loading.md](32-async-options-loading.md) — `onChange` с debounce + AbortSignal"],"sections":[{"heading":"31. ASYNC VALIDATOR","slug":"async-validator"}],"terms":[["async",23],["model",19],["email",16],["validatemodel",12],["const",12],["validateasync",10],["signal",9],["null",9],["await",9],["promise",8],["sync",8],["value",7],["правила",7],["return",7],["schema",7],["debounce",7],["через",6],["reformer",6],["core",6],["правило",5],["import",5],["from",5],["validate",5],["required",5],["boolean",5],["submit",5],["api",4],["asyncrule",4],["sig",4],["раннер",4],["definevalidationschema",4],["abortsignal",4],["fetch",4],["string",4],["res",4],["отмена",4],["прогон",4],["onchange",4],["username",4],["для",3],["это",3],["схемы",3],["validation",3],["json",3],["available",3],["cross",3],["seterrors",3],["возвращает",3],["нет",3],["false",3],["держи",3],["setchecking",3],["errors",3],["preload",3],["валидация",2],["validationerror",2],["оператором",2],["asyncrules",2],["валидации",2],["исполняется",2],["раннером",2],["параллельно",2],["all",2],["дожидается",2],["схема",2],["createmodel",2],["его",2],["flight",2],["checkemailunique",2],["try",2],["check",2],["code",2],["message",2],["сетевой",2],["блокирует",2],["разными",2],["операторами",2],["одном",2],["сигнале",2],["шаг",2],["как",2],["ambient",2],["прогоняет",2],["схему",2],["полей",2],["ошибки",2],["формы",2],["true",2],["если",2],["инлайн",2],["формат",2],["inn",2],["field",2],["pending",2],["только",2],["поэтому",2],["обычно",2],["checking",2],["нужен",2],["revalidatewhen",2],["defineformbehavior",2],["void",2],["снапшот",2],["get",2],["revalidate",2],["when",2],["options",2],["loading",2]]},{"id":"async-options-loading","file":"32-async-options-loading.md","title":"async-options-loading","purpose":"Для динамической подгрузки опций dropdown'а по значению другого поля (`region` → `city options`, `carBrand` → `carModel options`) — используй `onChange` из `@reformer/core/behaviors` + `form.field.updateComponentProps({ options })`. `onChange` даёт debounce и AbortSignal из коробки.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":["[11-async-watchfield.md](11-async-watchfield.md) — общий паттерн `onChange`","[29-async-preload.md](29-async-preload.md) — preload данных при инициализации","[31-async-validator-debounce.md](31-async-validator-debounce.md) — async **валидация** (не options)"],"sections":[{"heading":"32. ASYNC OPTIONS LOADING","slug":"async-options-loading"}],"terms":[["options",16],["async",14],["updatecomponentprops",12],["onchange",11],["signal",11],["region",10],["debounce",9],["form",8],["loading",7],["city",7],["model",7],["target",7],["value",6],["registrationaddress",6],["preload",6],["const",5],["при",5],["reformer",4],["core",4],["behaviors",4],["abortsignal",4],["string",4],["fetch",4],["return",4],["source",4],["true",4],["false",4],["resettarget",4],["для",3],["import",3],["from",3],["fetchcitiesbyregion",3],["opts",3],["await",3],["поле",3],["смене",3],["устаревший",3],["tvalue",3],["void",3],["reset",3],["опций",2],["carbrand",2],["carmodel",2],["даёт",2],["defineformbehavior",2],["type",2],["cityoption",2],["function",2],["promise",2],["res",2],["зависимое",2],["try",2],["catch",2],["aborterror",2],["запрос",2],["оператор",2],["readonlysignal",2],["loadoptionson",2],["toption",2],["fetcher",2],["data",2],["инициализации",2],["immediate",2],["watchfield",2],["validator",2]]},{"id":"validation-strategy","file":"33-validation-strategy.md","title":"useFormValidation — Единый выбор стратегии валидации","purpose":"Одна декларативная точка выбора «когда прогонять схему валидации» вместо ручной разводки `markAsTouched` + `validateModel` + `revalidateWhen` + `useState(pending)`. Аддитивный слой над функциональной схемой (`@reformer/core/validation`): переиспользует `validateModel` как ЕДИНСТВЕННЫЙ движок (роутинг ошибок в ноды, отмена устаревших прогонов, дедуп по `(model, schema)`), второго движка не заводит. Node-level `updateOn` / `debounce` на самой ноде поля (legacy-триггеры) этот API НЕ трогает.","keyConcepts":[],"api":"Основной способ — React-хук `useFormValidation` из `@reformer/core` (рядом с `useFormControl`).\nПод ним — headless-фабрика `createFormValidation` из `@reformer/core/validation` (для SSR / тестов /\nне-React потребителей).\n\n```typescript\n// React (@reformer/core)\nfunction useFormValidation<T>(args: {\n model: FormModel<T>;\n schema: ValidationSchema<T>; // СТАБИЛЬНАЯ ссылка (module-level const / useMemo)\n strategy?: ValidationStrategyKind; // default 'submit'\n debounce?: number; // мс, для live-фаз (change / live-часть afterFirstSubmit)\n liveAfterSubmit?: 'change' | 'blur'; // default 'change'\n}): { submit: () => Promise<boolean>; validate: () => Promise<boolean>; isValidating: boolean };\n\n// headless (@reformer/core/validation)\ntype ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';\ninterface ValidationStrategyOptions {\n strategy?: ValidationStrategyKind;\n debounce?: number;\n liveAfterSubmit?: 'change' | 'blur';\n}\ninterface FormValidationController {\n validate(): Promise<boolean>; // полный прогон, touch:true (раскрыть ВСЕ ошибки); переводит afterFirstSubmit в live-фазу\n start(): () => void; // армит реактивные подписки (ТОЛЬКО на клиенте) → dispose\n dispose(): void;\n readonly isValidating: boolean;\n readonly validating: ReadonlySignal<boolean>;\n}\nfunction createFormValidation<T>(\n model: FormModel<T>,\n schema: ValidationSchema<T>,\n options?: ValidationStrategyOptions,\n): FormValidationController;\n```\n\n- `useFormValidation` мемоизирует контроллер по `[model, schema, strategy, debounce, liveAfterSubmit]`,\n армит стратегию в `useEffect` (SSR-safe), а `submit()` = `controller.validate()`.\n- `createFormValidation` ЧИСТА до `start()` (никаких подписок) → SSR-safe; `start()` возвращает\n `dispose` и арминует триггеры только на клиенте.","patterns":[],"antiPatterns":[{"why":"schema пересоздаётся каждый рендер → контроллер и дедуп раннера ломаются","bad":"function MyForm() {\n const schema = defineValidationSchema<Form>(({ model }) => { … }); // новая ссылка на каждый рендер\n useFormValidation({ model, schema, strategy: 'change' });\n}","correctNote":"Стабильная ссылка: module-level const (или useMemo с пустыми deps)","correct":"const formValidation = defineValidationSchema<Form>(({ model }) => { … });\nfunction MyForm() {\n useFormValidation({ model, schema: formValidation, strategy: 'change' });\n}"},{"why":"Node-level updateOn (Слой B) И активная schema-стратегия на ОДНОМ поле → оба пишут ошибки в ноду → мерцание","bad":"// узел поля: { updateOn: 'blur', … } + useFormValidation({ strategy: 'blur', schema: включает это же поле })","correctNote":"Одно поле обслуживает ОДИН слой: либо node-level updateOn, либо schema-стратегия — не оба сразу"},{"why":"start() на сервере — арминует реактивные подписки при SSR","bad":"const ctrl = createFormValidation(model, schema, { strategy: 'change' });\nctrl.start(); // на сервере подписок быть не должно","correctNote":"Фабрика чиста до start(); армить только на клиенте (useFormValidation делает это в useEffect)","correct":"const ctrl = createFormValidation(model, schema, { strategy: 'change' });\nif (typeof window !== 'undefined') ctrl.start();"}],"examples":[],"troubleshooting":[],"seeAlso":["[28-submit-and-reset.md](./28-submit-and-reset.md) — submit-флоу и `validateModel` (движок под стратегией)","[27-revalidate-when.md](./27-revalidate-when.md) — ручные триггеры перевалидации (низкоуровневый примитив)","[31-async-validator-debounce.md](./31-async-validator-debounce.md) — async-правила и отмена устаревших прогонов","[13-multi-step.md](./13-multi-step.md) — per-step / полная валидация wizard (`validateStep` / `validateAll`)"],"sections":[{"heading":"Purpose","slug":"purpose-9"},{"heading":"API","slug":"api-8"},{"heading":"Examples","slug":"examples-8"},{"heading":"Anti-patterns","slug":"anti-patterns-8"},{"heading":"See also","slug":"see-also-10"}],"terms":[["model",22],["submit",22],["const",16],["schema",14],["strategy",14],["change",12],["form",11],["useformvalidation",10],["reformer",10],["debounce",10],["core",9],["только",9],["blur",9],["validate",9],["createformvalidation",8],["start",8],["afterfirstsubmit",7],["ctrl",7],["level",6],["function",6],["live",6],["boolean",6],["email",6],["шага",6],["validation",5],["стратегия",5],["definevalidationschema",5],["react",5],["ssr",5],["typescript",5],["dispose",5],["import",5],["from",5],["при",5],["слой",4],["ошибок",4],["updateon",4],["поля",4],["под",4],["usememo",4],["default",4],["liveaftersubmit",4],["isvalidating",4],["true",4],["ошибки",4],["клиенте",4],["required",4],["username",4],["password",4],["async",4],["step",4],["config",4],["validatemodel",3],["дедуп",3],["node",3],["триггеры",3],["api",3],["headless",3],["ссылка",3],["module",3],["validationstrategykind",3],["promise",3],["type",3],["раскрыть",3],["все",3],["армит",3],["useeffect",3],["арминует",3],["touched",3],["false",3],["каждый",3],["тихо",3],["minlength",3],["registrationdata",3],["string",3],["registrationvalidation",3],["onsubmit",3],["await",3],["formfield",3],["control",3],["wizard",3],["definesteps",3],["usewizardstepvalidation",3],["validateall",3],["formwizard",3],["confirm",3],["поле",3],["стратегии",2],["валидации",2],["когда",2],["ручной",2],["markastouched",2],["движок",2],["отмена",2],["устаревших",2],["прогонов",2],["раскрытие",2],["правила",2],["фабрика",2],["для",2],["formmodel",2],["validationschema",2],["стабильная",2],["number",2],["interface",2],["validationstrategyoptions",2],["formvalidationcontroller",2],["прогон",2],["touch",2],["переводит",2],["фазу",2],["void",2],["реактивные",2],["подписки",2],["readonly",2],["контроллер",2],["стратегию",2],["safe",2],["чиста",2],["подписок",2],["всё",2],["фокуса",2],["ввод",2],["один",2],["дальше",2],["далее",2],["живую",2],["tsx",2],["createmodel",2],["createform",2],["раннера",2],["нет",2],["return",2],["button",2],["внутри",2],["шаг",2],["снимает",2],["per",2],["validatestep",2],["правил",2],["loan",2],["applicant",2],["валидация",2],["loanstepbody",2],["рендер",2],["myform",2],["formvalidation",2],["оба",2],["это",2],["либо",2],["сервере",2],["and",2],["reset",2],["revalidate",2],["when",2],["validator",2],["multi",2]]}],"symbols":[{"name":"aggregateInto","kind":"function","signature":"export function aggregateInto<TItem>(\n array: object,\n derive: (rows: TItem[]) => Array<{ index: number; patch: Partial<TItem> }>\n): void","summary":"Агрегатная запись в строки массива.","description":"Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список\n`{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на\nфинальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на\nпромежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).","params":[],"example":"// последняя строка = 100 − Σ(остальные)\naggregateInto(model.$.rows, (rows) => {\n const n = rows.length; if (n === 0) return [];\n const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);\n return [{ index: n - 1, patch: { percent: 100 - others } }];\n});","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","array-operations"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"AnyFunction","kind":"type","signature":"export type AnyFunction = (...args: never[]) => unknown;","summary":"Тип для проверки на функцию в conditional types Используется вместо Function для type narrowing","description":"Тип для проверки на функцию в conditional types\nИспользуется вместо Function для type narrowing","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"apply","kind":"function","signature":"export function apply<T>(...schemas: ValidationSchema<T>[]): void","summary":"Композиция под-схем в текущую (над той же моделью scope).","description":"Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","multi-step","extended-mistakes","nonexistent-api","sync-fields","submit-and-reset"],"sourcePath":"src/form/validation/operators.ts","entries":["./behaviors","./validation"],"package":"@reformer/core","usage":4},{"name":"applyEach","kind":"function","signature":"export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void","summary":"Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).","description":"Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).\nРеагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.\n\nПод-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).\n- Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.\n- Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был\n МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что\n рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ\n к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).","params":[],"example":"applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {\n compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда\n enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива\n}));","examplesCount":1,"topics":["api-reference","api-signatures","troubleshooting","complete-import","extended-mistakes","array-operations"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"ArrayControlState","kind":"interface","signature":"export interface ArrayControlState<T> {\n /**\n * Массив текущих значений всех элементов.\n *\n * @example\n * ```tsx\n * const { value } = useFormControl(phonesArray);\n * console.log(value);\n * // [{ type: 'mobile', number: '+1234567890' }, { type: 'home', number: '+0987654321' }]\n * ```\n */\n value: T[];\n\n /**\n * Количество элементов в массиве.\n * Эквивалентно value.length, но оптимизировано для реактивности.\n *\n * @example\n * ```tsx\n * const { length } = useFormControl(itemsArray);\n *\n * return (\n * <div>\n * <span>Items: {length}</span>\n * {length >= 10 && <span>Maximum reached</span>}\n * </div>\n * );\n * ```\n */\n length: number;\n\n /**\n * Флаг асинхронной валидации.\n * `true` когда выполняется асинхронный валидатор массива или любого элемента.\n */\n pending: boolean;\n\n /**\n * Массив ошибок валидации уровня массива.\n * Не включает ошибки отдельных элементов.\n *\n * @example\n * ```tsx\n * // Валидатор массива\n * validators.apply(phonesArray, {\n * validator: (phones) => phones.length >= 1,\n * message: 'At least one phone required'\n * });\n *\n * // В компоненте\n * const { errors } = useFormControl(phonesArray);\n * // errors содержит ошибку \"At least one phone required\" если массив пуст\n * ```\n */\n errors: ValidationError[];\n\n /**\n * Флаг валидности массива и всех его элементов.\n * `true` только когда массив и все вложенные элементы валидны.\n */\n valid: boolean;\n\n /**\n * Флаг невалидности.\n * `true` когда есть ошибки в массиве или любом элементе.\n */\n invalid: boolean;\n\n /**\n * Флаг взаимодействия.\n * `true` после взаимодействия с любым элементом массива.\n */\n touched: boolean;\n\n /**\n * Флаг изменения.\n * `true` когда значение массива отличается от начального.\n *\n * @example\n * ```tsx\n * const { dirty } = useFormControl(itemsArray);\n *\n * return (\n * <div>\n * {dirty && <span>* Unsaved changes</span>}\n * <button disabled={!dirty}>Save</button>\n * </div>\n * );\n * ```\n */\n dirty: boolean;\n\n /**\n * Флаг отключения массива.\n * `true` когда массив отключён (`ArrayNode.disable()`), в том числе через\n * распространение disable от родительской группы.\n *\n * Используйте для отключения UI-действий добавления/удаления, когда массив\n * структурно неизменяем.\n *\n * @example\n * ```tsx\n * const { disabled } = useFormControl(itemsArray);\n *\n * return <button disabled={disabled} onClick={() => control.push()}>Add</button>;\n * ```\n */\n disabled: boolean;\n}","summary":"Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.","description":"Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.\n\nСодержит реактивные данные массива: значения элементов, длину, состояние валидации\nи флаги взаимодействия.","params":[],"example":"Список с динамическим добавлением\n```tsx\ninterface Phone {\ntype: string;\nnumber: string;\n}\n\ninterface Props {\ncontrol: ArrayNode<Phone>;\n}\n\nfunction PhoneList({ control }: Props) {\nconst { length, valid } = useFormControl(control);\n\nreturn (\n<div>\n{control.map((item, index) => (\n<PhoneItem\n key={item.id}\n control={item}\n onRemove={() => control.removeAt(index)}\n/>\n))}\n\n{length === 0 && <p>No phones added</p>}\n\n<button onClick={() => control.push({ type: 'mobile', number: '' })}>\nAdd Phone\n</button>\n\n{!valid && <p className=\"error\">Please fix phone errors</p>}\n</div>\n);\n}\n```","examplesCount":1,"related":["useFormControl","FieldControlState"],"sourcePath":"src/platforms/react/hooks/types.ts","entries":["."],"package":"@reformer/core"},{"name":"ArrayNode","kind":"class","signature":"export class ArrayNode<T extends object> extends FormNode<T[]> {\n // ============================================================================\n // Приватные поля\n // ============================================================================ /* … */ }","summary":"ArrayNode - массив форм с реактивным состоянием","description":"ArrayNode - массив форм с реактивным состоянием","params":[],"example":"```typescript\nconst array = new ArrayNode({\n title: { value: '', component: InputField },\n price: { value: 0, component: InputField },\n});\n\narray.push({ title: 'Item 1', price: 100 });\narray.at(0)?.title.setValue('Updated');\nconsole.log(array.length.value); // 1\n```","examplesCount":1,"topics":["api-reference","common-mistakes","complete-import","arrays","type-safety-recipes"],"sourcePath":"src/form/nodes/array-node.ts","entries":["."],"package":"@reformer/core","usage":8},{"name":"ArrayNodeLike","kind":"interface","signature":"export interface ArrayNodeLike {\n at(index: number): FormNode<unknown> | undefined;\n length: unknown;\n}","summary":"Интерфейс для узлов, похожих на ArrayNode (с методом at) Используется для duck typing при обходе путей","description":"Интерфейс для узлов, похожих на ArrayNode (с методом at)\nИспользуется для duck typing при обходе путей","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"AsyncRule","kind":"type","signature":"export type AsyncRule<TField> = (\n value: TField,\n ctx: { signal: AbortSignal }\n) => Promise<ValidationError | null>;","summary":"Асинхронное правило поля.","description":"Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный\nпрогон той же схемы отменяет предыдущий). Сетевой сбой не должен блокировать — ловите и возвращайте `null`.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-mistakes","complete-import","nonexistent-api","async-validator-debounce"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core","usage":9},{"name":"AsyncValidatorFn","kind":"type","signature":"export type AsyncValidatorFn<T = FormValue> = (\n value: T,\n options?: AsyncValidatorOptions\n) => Promise<ValidationError | null>;","summary":"Асинхронная функция валидации","description":"Асинхронная функция валидации","params":[["value","- Значение для валидации"],["options","- Опции валидации (опционально)"]],"returns":"Promise с ошибкой валидации или null если значение валидно","example":"```typescript\n// Простой валидатор (без поддержки отмены)\nconst emailExists: AsyncValidatorFn<string> = async (value) => {\n const exists = await checkEmail(value);\n return exists ? { code: 'exists', message: 'Email already exists' } : null;\n};\n\n// Валидатор с поддержкой отмены\nconst emailExistsAbortable: AsyncValidatorFn<string> = async (value, options) => {\n const exists = await fetch(`/api/check-email?email=${value}`, {\n signal: options?.signal // Передаём signal в fetch для отмены запроса\n });\n return exists ? { code: 'exists', message: 'Email already exists' } : null;\n};\n```","examplesCount":1,"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"AsyncValidatorOptions","kind":"interface","signature":"export interface AsyncValidatorOptions {\n /**\n * AbortSignal для отмены валидации\n * Позволяет отменить асинхронную операцию при новой валидации\n */\n signal?: AbortSignal;\n}","summary":"Опции для асинхронного валидатора","description":"Опции для асинхронного валидатора","params":[],"examplesCount":0,"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"BehaviorCleanup","kind":"type","signature":"export type BehaviorCleanup = () => void;","summary":"Функция отписки от behavior-эффекта.","description":"Функция отписки от behavior-эффекта.","params":[],"examplesCount":0,"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"BehaviorScope","kind":"type","signature":"export type BehaviorScope<T> = { model: FormModel<T>; form: FormProxy<T> };","summary":"Контекст схемы поведения: модель (значения/сигналы) + форма (ноды).","description":"Контекст схемы поведения: модель (значения/сигналы) + форма (ноды).","params":[],"examplesCount":0,"sourcePath":"src/form/behaviors/types.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"buildValidation","kind":"function","signature":"export function buildValidation<T>(\n model: FormModel<T>,\n validation?: FormValidation<T> | ValidationSchema<T>\n): FormValidationBundle<T> | undefined","summary":"Собрать {@link FormValidationBundle} из правил.","description":"Собрать {@link FormValidationBundle} из правил. Принимает либо готовую схему (сахар для простых\nформ), либо {@link FormValidation} с шагами и стратегией.\n\nСхема для `validateAll` собирается ОДИН раз на бандл — иначе ломается дедупликация прогонов.","params":[["model","- Модель, по которой идут прогоны."],["validation","- Правила; `undefined` — валидации у формы нет."]],"returns":"Бандл валидации либо `undefined`, если правила не заданы.","example":"```ts\nconst v = buildValidation(model, {\n steps: { loan: loanRules, applicant: applicantRules, confirm: null },\n extras: crossFieldRules,\n strategy: 'blur',\n});\nawait v!.validateStep(1); // правила шага `loan`\nawait v!.validateAll(); // все шаги + extras\n```","examplesCount":1,"sourcePath":"src/form/validation/config.ts","entries":["."],"package":"@reformer/core"},{"name":"ChangeContext","kind":"interface","signature":"export interface ChangeContext {\n signal: AbortSignal;\n}","summary":"Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка.","description":"Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка.","params":[],"examplesCount":0,"sourcePath":"src/form/behaviors/types.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"compute","kind":"function","signature":"export function compute<R>(\n target: Signal<R>,\n read: () => R,\n options?: { when?: () => boolean }\n): void","summary":"Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов.","description":"Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","extended-mistakes","project-structure","nonexistent-api","conditional-fields","reading-values","compute-vs-watch","array-operations","cycle-detection","copy-from","sync-fields","reset-when","transform-value","revalidate-when","type-safety-recipes"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":24},{"name":"computeFrom","kind":"function","signature":"export function computeFrom<R>(\n sources: ReadonlySignal<any>[],\n target: Signal<R>,\n fn: (...values: any[]) => R,\n options?: { when?: (...values: any[]) => boolean }\n): BehaviorCleanup","summary":"Вычисляемое поле: `target = fn(...sourceValues)` при изменении источников.","description":"Вычисляемое поле: `target = fn(...sourceValues)` при изменении источников.","params":[["sources","Сигналы-источники (`model.$.a`, `model.$.b`)."],["target","Сигнал-цель (`model.$.total`)."],["fn","Функция вычисления значения."]],"returns":"Cleanup для отписки.","example":"```typescript\ncomputeFrom([model.$.price, model.$.qty], model.$.total, (price, qty) => price * qty);\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","extended-mistakes","reading-values","compute-vs-watch","cycle-detection","type-safety-recipes"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":6},{"name":"ConfigWithSchema","kind":"interface","signature":"export interface ConfigWithSchema {\n schema: unknown;\n initialItems?: unknown[];\n}","summary":"Конфиг с полем schema (для ArrayConfig)","description":"Конфиг с полем schema (для ArrayConfig)","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"ConfigWithValue","kind":"interface","signature":"export interface ConfigWithValue {\n value: unknown;\n}","summary":"Конфиг с полем value (для извлечения значений)","description":"Конфиг с полем value (для извлечения значений)","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"copyFrom","kind":"function","signature":"export function copyFrom<T>(\n source: ReadonlySignal<T>,\n target: Signal<T>,\n options?: { when?: () => boolean; transform?: (value: T) => T }\n): BehaviorCleanup","summary":"Копирование значения `source → target` (опционально по условию/с трансформом).","description":"Копирование значения `source → target` (опционально по условию/с трансформом).","params":[],"example":"```typescript\ncopyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail });\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","project-structure","conditional-fields","reading-values","cycle-detection","copy-from","sync-fields","transform-value"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":10},{"name":"CORE_RUNTIME_TOKEN","kind":"const","signature":"export const CORE_RUNTIME_TOKEN: object","summary":"Штамп копии рантайма ядра.","description":"Штамп копии рантайма ядра.\n\nЕдинственное назначение — быть объектом, уникальным в пределах ОДНОЙ копии модуля. Две\nнезависимо загруженные копии `@reformer/core` дадут два разных объекта, и по их несовпадению\nguard распознаёт дубль рантайма.\n\nПочему именно объект, а не строка с версией: две копии ОДНОЙ версии ломают всё ровно так же,\nкак две копии разных версий, — раздваиваются модульные `WeakMap` (`derived-registry`,\n`signal-node-registry`) и перестают работать проверки `instanceof Signal`. Сравнение версий\nтакой случай пропустило бы.","params":[],"example":"```ts\nimport { CORE_RUNTIME_TOKEN } from '@reformer/core';\nimport { stampRuntime } from '@reformer/form-registry/guard';\n\nstampRuntime('@reformer/core', CORE_RUNTIME_TOKEN);\n```","examplesCount":1,"sourcePath":"src/runtime-token.ts","entries":["."],"package":"@reformer/core"},{"name":"CoreForm","kind":"interface","signature":"export interface CoreForm<T> {\n model: FormModel<T>;\n form: FormProxy<T>;\n validation?: FormValidationBundle<T>;\n}","summary":"Результат {@link createCoreForm}: модель, форма и (если заданы правила) собранная валидация.","description":"Результат {@link createCoreForm}: модель, форма и (если заданы правила) собранная валидация.","params":[],"examplesCount":0,"sourcePath":"src/form/create-core-form.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"createCoreForm","kind":"function","signature":"export function createCoreForm<T extends object>(config: CreateCoreFormConfig<T>): CoreForm<T>","summary":"Собрать модель, форму и валидацию за один проход.","description":"Собрать модель, форму и валидацию за один проход.","params":[["config","- {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,\n`validation`, `seed`, `setup`."]],"returns":"","example":"```tsx\nconst credit = useFormBundle(() =>\n createCoreForm<CreditForm>({\n model: createCreditModel(),\n schema: buildCreditSchema,\n behavior: creditBehavior,\n validation: { steps: { loan: loanRules, confirm: null }, extras: crossRules },\n })\n);\nreturn <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;\n```","examplesCount":1,"topics":["quick-start","project-structure"],"sourcePath":"src/form/create-core-form.ts","entries":["."],"package":"@reformer/core","usage":16},{"name":"CreateCoreFormConfig","kind":"interface","signature":"export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {\n /**\n * Билдер схемы формы. Именно функция, а не готовое дерево: листья схемы держат сами сигналы\n * модели (`value: model.$.email`), поэтому построить дерево до модели нечем.\n */\n schema?: (model: FormModel<T>) => FormSchemaNode;\n}","summary":"Конфиг {@link createCoreForm}.","description":"Конфиг {@link createCoreForm}.","params":[],"examplesCount":0,"sourcePath":"src/form/create-core-form.ts","entries":["."],"package":"@reformer/core"},{"name":"createForm","kind":"function","signature":"export function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;","summary":"Создать форму из {@link FormModel} + единой схемы (архитектура M1, рекомендуемый путь).","description":"Создать форму из {@link FormModel} + единой схемы (архитектура M1, рекомендуемый путь).","params":[["args","- Модель данных, layout-схема (component/componentProps) и (опционально) поведение"]],"returns":"Типизированная форма с Proxy-доступом к полям","example":"Архитектура M1: `createModel` + layout-схема + `createForm({ model, schema })`\n```typescript\nimport { createModel, createForm } from '@reformer/core';\nimport { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';\nimport { required, email, minLength } from '@reformer/core/validators';\n\ninterface UserForm {\nemail: string;\npassword: string;\n}\n\nconst model = createModel<UserForm>({ email: '', password: '' });\n\n// Layout-схема НЕ несёт validators — только component/componentProps.\nconst schema = {\nchildren: [\n{ value: model.$.email, component: InputField },\n{ value: model.$.password, component: InputField },\n],\n};\n\nconst form = createForm<UserForm>({ model, schema });\nform.email.setValue('test@mail.com');\n\n// Валидация — ОТДЕЛЬНАЯ схема, прогон внешним раннером (ошибки сами доезжают до нод):\nconst validation = defineValidationSchema<UserForm>(({ model }) => {\nvalidate(model.$.email, [required(), email()]);\nvalidate(model.$.password, [required(), minLength(8)]);\n});\nconst ok = await validateModel(model, validation);\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","troubleshooting","complete-import","formschema","arrays","ui-components","nonexistent-api","conditional-fields","compute-vs-watch","copy-from","reset-when","transform-value","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/create-form.ts","entries":["."],"package":"@reformer/core","usage":11},{"name":"CreateFormConfigBase","kind":"interface","signature":"export interface CreateFormConfigBase<T, B> {\n /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */\n initial?: T;\n /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */\n model?: FormModel<T>;\n /** Декларативное поведение модели (compute/copyFrom/enableWhen/onChange). */\n behavior?: FormBehavior<T>;\n /** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */\n validation?: FormValidation<T> | ValidationSchema<T>;\n /**\n * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту\n * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на\n * инициализации не срабатывают).\n */\n seed?: (model: FormModel<T>) => void;\n /**\n * Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за\n * `createForm`: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),\n * поэтому такой префилл выполняется здесь.\n */\n setup?: (bundle: B) => void;\n}","summary":"Общая часть конфига всех фабрик формы.","description":"Общая часть конфига всех фабрик формы.","params":[],"examplesCount":0,"sourcePath":"src/form/create-core-form.ts","entries":["."],"package":"@reformer/core"},{"name":"createFormFromModel","kind":"function","signature":"export function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>","summary":"Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).","description":"Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).\n\nЗначения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и\nссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру\nмодели, привязывает конфиг поля (component/componentProps) из схемы, материализует\ntop-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/\nроутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).\n\nОбычно вызывается неявно через {@link createForm} с аргументом `{ model, schema }` — прямой вызов\nнужен редко (например, для построения формы элемента массива).","params":[["args","- Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}"]],"returns":"Типизированная форма с Proxy-доступом к полям {@link FormProxy}","example":"Форма из модели + схемы (эквивалент `createForm({ model, schema })`)\n```typescript\nimport { createModel, createFormFromModel } from '@reformer/core';\n\ninterface Form {\nemail: string;\nprofile: { name: string; age: number };\n}\n\nconst model = createModel<Form>({ email: '', profile: { name: '', age: 0 } });\nconst schema = {\ncomponent: Section,\nchildren: [\n// Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.\n{ value: model.$.email, component: InputField },\n// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)\n{ value: model.$.profile.name, component: InputField },\n],\n};\n\nconst form = createFormFromModel<Form>({ model, schema });\n\n// Двусторонняя связь нода ↔ модель:\nform.email.setValue('user@mail.com');\nconsole.log(model.email); // 'user@mail.com'\n```","examplesCount":1,"related":["createForm"],"sourcePath":"src/form/create-form.ts","entries":["."],"package":"@reformer/core"},{"name":"CreateFormFromModelArgs","kind":"interface","signature":"export interface CreateFormFromModelArgs<T> {\n /** Реактивная модель данных (источник истины значений). */\n model: FormModel<T>;\n /**\n * Единая Schema (дерево узлов {@link FormSchemaNode}). createForm обходит её и привязывает конфиг\n * поля к ноде по идентичности сигнала (`node.value === model.$.path`). Опциональна.\n */\n schema?: FormSchemaNode;\n /**\n * Декларативная схема поведения ({@link defineFormBehavior}). Запускается ПОСЛЕ построения нод и\n * заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.\n */\n behavior?: FormBehavior<T>;\n}","summary":"Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel}, конфиг полей (component/componentProps) — из единой схемы.","description":"Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},\nконфиг полей (component/componentProps) — из единой схемы.","params":[],"examplesCount":0,"sourcePath":"src/form/create-form.ts","entries":["."],"package":"@reformer/core"},{"name":"createFormValidation","kind":"function","signature":"export function createFormValidation<T>(\n model: FormModel<T>,\n schema: ValidationSchema<T>,\n options: ValidationStrategyOptions = {}\n): FormValidationController","summary":"Собрать контроллер валидации формы с выбранной стратегией запуска.","description":"Собрать контроллер валидации формы с выбранной стратегией запуска. Переиспользует\n{@link validateModel} как единственный движок прогона.\n\nФабрика ЧИСТАЯ до `start()` (никаких подписок) — безопасна для SSR/headless. Реактивные триггеры\nарминуются только в `start()` (в React — из `useEffect`).","params":[["model","- Модель данных."],["schema","- Схема валидации. **Стабильная ссылка** (иначе `validateModel` не отменит устаревший прогон)."],["options","- {@link ValidationStrategyOptions}."]],"example":"```ts\nconst ctrl = createFormValidation(model, schema, { strategy: 'afterFirstSubmit', debounce: 300 });\nconst dispose = ctrl.start(); // на клиенте\nconst ok = await ctrl.validate(); // submit → раскрыть все ошибки\ndispose();\n```","examplesCount":1,"topics":["validation-strategy"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"createModel","kind":"function","signature":"export function createModel<T extends object>(initial: T): FormModel<T>","summary":"Создать реактивную модель данных формы (слой M1).","description":"Создать реактивную модель данных формы (слой M1).","params":[["initial","Начальные значения (объект). Определяют форму данных и initial-снимок."]],"returns":"","example":"```typescript\nconst model = createModel<{ email: string; profile: { name: string }; tags: string[] }>({\n email: '',\n profile: { name: '' },\n tags: [],\n});\nmodel.email = 'a@b.c';\nmodel.$.email.value; // 'a@b.c' (сигнал)\n// вложенная объект-группа — под-модель FormModel (value-доступ + `.$` + API):\nmodel.profile.name = 'Ada'; // value-запись\nmodel.$.profile.name.value; // 'Ada' (сигнал; ≡ model.profile.$.name у под-модели)\nmodel.profile.get(); // { name: 'Ada' }\nmodel.tags.push('x');\nmodel.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tags: ['x'] }\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","form-types","formschema","arrays","project-structure","ui-components","nonexistent-api","conditional-fields","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/model/create-model.ts","entries":[".","./model"],"package":"@reformer/core","usage":9},{"name":"cross","kind":"function","signature":"export function cross<TSnapshot>(\n sig: PathAwareSignal<unknown>,\n fn: (form: TSnapshot) => ValidationError | null\n): void","summary":"Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.","description":"Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.\nДля элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),\nт.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","arrays","extended-mistakes","project-structure","nonexistent-api","conditional-fields","compute-vs-watch","array-operations","revalidate-when","submit-and-reset","type-safety-recipes","async-validator-debounce"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":34},{"name":"defer","kind":"function","signature":"export function defer(fn: () => void): void","summary":"Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected».","description":"Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected».","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"defineFormBehavior","kind":"function","signature":"export function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>","summary":"Описать поведение формы декларативно.","description":"Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.","params":[],"example":"```ts\nexport const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {\n compute(model.$.total, () => model.price * model.qty);\n enableWhen([model.$.city], () => Boolean(model.country));\n onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","formschema","async-watchfield","array-cleanup","extended-mistakes","project-structure","nonexistent-api","conditional-fields","reading-values","compute-vs-watch","array-operations","copy-from","sync-fields","reset-when","transform-value","revalidate-when","async-preload","type-safety-recipes","async-validator-debounce","async-options-loading"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core","usage":8},{"name":"defineValidationSchema","kind":"function","signature":"export function defineValidationSchema<T>(schema: ValidationSchema<T>): ValidationSchema<T>","summary":"Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`).","description":"Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.","params":[],"example":"```ts\nexport const step1 = defineValidationSchema<LoanForm>(({ model }) => {\n validate(model.$.loanAmount, [required(), min(50000)]);\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","complete-import","formschema","arrays","multi-step","extended-mistakes","project-structure","nonexistent-api","conditional-fields","array-operations","revalidate-when","submit-and-reset","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"package":"@reformer/core","usage":25},{"name":"disableWhen","kind":"function","signature":"export function disableWhen(\n target: ReadonlySignal<unknown>,\n condition: () => boolean,\n options?: { resetOnDisable?: boolean }\n): BehaviorCleanup","summary":"Условное выключение поля (инверсия {@link enableWhen}).","description":"Условное выключение поля (инверсия {@link enableWhen}). Резолвит ноду по сигналу-цели через реестр\nсигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).\n`condition` реактивен (читает свои сигналы модели).","params":[["target","Сигнал-цель поля (`model.$.<path>`)."],["condition","Реактивное условие; при `true` поле выключается."]],"returns":"Cleanup для отписки.","example":"```typescript\n// Поле скидки недоступно, пока не выбран промо-тариф\ndisableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: true });\n```","examplesCount":1,"topics":["api-reference","api-signatures","troubleshooting","complete-import"],"sourcePath":"src/form/behaviors/node.ts","entries":[".","./behaviors"],"package":"@reformer/core","usage":4},{"name":"each","kind":"function","signature":"export function each<U extends object>(\n arr: ModelArray<U>,\n itemFn: (item: FormModel<U>) => void\n): void","summary":"Применить под-правила к КАЖДОМУ элементу текущего массива модели.","description":"Применить под-правила к КАЖДОМУ элементу текущего массива модели.\n`U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-mistakes","complete-import","arrays","extended-mistakes","project-structure","nonexistent-api","array-operations","submit-and-reset"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":5},{"name":"eachLeafSignal","kind":"function","signature":"export function eachLeafSignal<T>(\n model: FormModel<T>,\n visit: (signal: PathAwareSignal<unknown>) => void\n): void","summary":"Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.","description":"Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.\n\nВнутри реактивного `effect` служит подпиской «любое поле изменилось»: `visit(sig => void sig.value)`\nподписывает на значения листьев, а обход массивов через `items.value` — на их состав. Так строятся\nтриггеры `change`/`blur` стратегий валидации (см. `createFormValidation`), тем же паттерном, что\n`revalidateWhen`, но без ручного перечисления зависимостей.","params":[],"examplesCount":0,"sourcePath":"src/model/create-model.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"effect","kind":"function","signature":"export function effect(fn: () => void | (() => void)): void","summary":"Реактивный эффект (авто-dispose).","description":"Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup.","params":[],"examplesCount":0,"topics":["api-reference","common-patterns","async-watchfield","array-cleanup","reading-values","compute-vs-watch","cycle-detection","transform-value","revalidate-when"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"email","kind":"function","signature":"export function email<TForm = unknown, TField extends string | null | undefined = string>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора формата email.","description":"Фабрика валидатора формата email.\n\nПроверяет по упрощённому regex `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`. Пустые значения\n(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для строкового поля","example":"Проверка формата email\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, email } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.email, [required(), email({ message: 'Введите корректный email' })]);\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","complete-import","form-types","formschema","ui-components","nonexistent-api","conditional-fields","reading-values","copy-from","transform-value","submit-and-reset","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/validators/email.ts","entries":["./validators"],"package":"@reformer/core","usage":89},{"name":"enableWhen","kind":"function","signature":"export function enableWhen(\n target: ReadonlySignal<unknown>,\n condition: () => boolean,\n options?: { resetOnDisable?: boolean }\n): BehaviorCleanup","summary":"Условное включение поля (state-операция).","description":"Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр\nсигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при\n`resetOnDisable`). `condition` реактивен (читает свои сигналы модели).\n\nЗапись состояния отложена через `runOutsideEffect` (микротаск) для защиты от «Cycle detected».\n⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,\nэлемент массива, который строится per-item).\n\n⚠️ Если то же поле одновременно является целью `compute`/`computeFrom`, обязательно ограничь\ncompute тем же условием (`when`), что и это `enableWhen`. Механизмы не согласованы: `enableWhen`\nработает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую\nв сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.\nПоэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с\n`resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`\nвключают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,\nкоторое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.","params":[],"example":"```typescript\nenableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });\n\n// Поле-цель compute, которое ТАКЖЕ в resetOnDisable enableWhen: compute обязан нести тот же when,\n// иначе initialPayment заново заполнится в выключенном состоянии и утечёт в submit.\ncompute(model.$.initialPayment, () => model.propertyValue * 0.2, {\n when: () => model.loanType === 'mortgage',\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","formschema","project-structure","nonexistent-api","conditional-fields","reading-values","cycle-detection","reset-when","submit-and-reset","type-safety-recipes"],"sourcePath":"src/form/behaviors/node.ts","entries":[".","./behaviors"],"package":"@reformer/core","usage":18},{"name":"ErrorFilterOptions","kind":"interface","signature":"export interface ErrorFilterOptions {\n /** Фильтр по коду ошибки */\n code?: string | string[];\n\n /** Фильтр по сообщению (поддерживает частичное совпадение) */\n message?: string;\n\n /** Фильтр по параметрам ошибки */\n params?: Record<string, FormValue>;\n\n /** Кастомный предикат для фильтрации */\n predicate?: (error: ValidationError) => boolean;\n}","summary":"Опции для фильтрации ошибок в методе getErrors()","description":"Опции для фильтрации ошибок в методе getErrors()","params":[],"examplesCount":0,"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"ErrorStrategy","kind":"enum","signature":"export enum ErrorStrategy {\n /**\n * Пробросить ошибку дальше (throw)\n * Используется когда ошибка критична и должна остановить выполнение\n */\n THROW = 'throw',\n\n /**\n * Залогировать и проглотить ошибку (продолжить выполнение)\n * Используется когда ошибка не критична\n */\n LOG = 'log',\n\n /**\n * Конвертировать ошибку в ValidationError\n * Используется в async validators для отображения ошибки валидации пользователю\n */\n CONVERT = 'convert',\n}","summary":"Стратегия обработки ошибок Определяет, что делать с ошибкой после логирования","description":"Стратегия обработки ошибок\n\nОпределяет, что делать с ошибкой после логирования","params":[],"examplesCount":0,"sourcePath":"src/form/validation/error-handler.ts","entries":["."],"package":"@reformer/core"},{"name":"exclusiveFlag","kind":"function","signature":"export function exclusiveFlag<TItem>(\n array: object,\n getFlag: (row: FormModel<TItem>) => Signal<boolean>\n): void","summary":"Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).","description":"Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).\nКогда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:\nновые строки с флагом false исключения не запускают.","params":[],"example":"exclusiveFlag(model.$.contacts, (row) => row.$.primary);","examplesCount":1,"topics":["api-reference","api-signatures","complete-import"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"FieldConfig","kind":"interface","signature":"export interface FieldConfig<T> {\n /**\n * Начальное значение-литерал (legacy-путь). Под архитектурой M1 значение приходит из\n * {@link FieldConfig.valueSignal} (сигнал {@link FormModel}); тогда `value` не требуется.\n */\n value?: T | null;\n /**\n * Сигнал значения из {@link FormModel} (M1). Если задан — служит источником истины значения\n * поля (нода не владеет значением, а ссылается на этот сигнал). Имеет приоритет над `value`.\n */\n valueSignal?: Signal<T>;\n /**\n * UI-компонент поля. Опционален: core-часть можно использовать без ссылки на компонент\n * (значение/валидация работают без UI; компонент нужен только для рендеринга).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n component?: ComponentType<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n componentProps?: any;\n validators?: ValidatorFn<T>[];\n asyncValidators?: AsyncValidatorFn<T>[];\n disabled?: boolean;\n updateOn?: 'change' | 'blur' | 'submit';\n /** Задержка (в мс) перед запуском асинхронной валидации */\n debounce?: number;\n}","summary":"Конфигурация поля","description":"Конфигурация поля","params":[],"examplesCount":0,"topics":["api-reference","common-mistakes","complete-import","arrays","nonexistent-api","array-operations"],"sourcePath":"src/form/types/deep-schema.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldControlState","kind":"interface","signature":"export interface FieldControlState<T> {\n /**\n * Текущее значение поля.\n *\n * @example\n * ```tsx\n * const { value } = useFormControl(emailField);\n * console.log(value); // \"user@example.com\"\n * ```\n */\n value: T;\n\n /**\n * Флаг асинхронной валидации или загрузки.\n * `true` когда выполняется асинхронный валидатор.\n *\n * @example\n * ```tsx\n * const { pending } = useFormControl(usernameField);\n *\n * return (\n * <div>\n * <input {...props} />\n * {pending && <Spinner size=\"small\" />}\n * </div>\n * );\n * ```\n */\n pending: boolean;\n\n /**\n * Флаг отключения поля.\n * `true` когда поле недоступно для редактирования.\n *\n * @example\n * ```tsx\n * const { disabled, value } = useFormControl(field);\n *\n * return (\n * <input\n * value={value}\n * disabled={disabled}\n * className={disabled ? 'opacity-50' : ''}\n * />\n * );\n * ```\n */\n disabled: boolean;\n\n /**\n * Массив ошибок валидации.\n * Пустой массив означает отсутствие ошибок.\n *\n * @example\n * ```tsx\n * const { errors } = useFormControl(field);\n *\n * return (\n * <ul className=\"error-list\">\n * {errors.map((error, i) => (\n * <li key={i}>{error.message}</li>\n * ))}\n * </ul>\n * );\n * ```\n */\n errors: ValidationError[];\n\n /**\n * Флаг валидности поля.\n * `true` когда поле прошло все валидации (errors.length === 0).\n *\n * @example\n * ```tsx\n * const { valid } = useFormControl(field);\n *\n * return (\n * <input className={valid ? 'border-green' : 'border-gray'} />\n * );\n * ```\n */\n valid: boolean;\n\n /**\n * Флаг невалидности поля.\n * `true` когда есть ошибки валидации (errors.length > 0).\n * Противоположность {@link valid}.\n *\n * @example\n * ```tsx\n * const { invalid } = useFormControl(field);\n *\n * return (\n * <input\n * aria-invalid={invalid}\n * className={invalid ? 'border-red' : ''}\n * />\n * );\n * ```\n */\n invalid: boolean;\n\n /**\n * Флаг взаимодействия с полем.\n * `true` после того как поле потеряло фокус (blur) хотя бы один раз.\n *\n * @example\n * ```tsx\n * const { touched, invalid } = useFormControl(field);\n *\n * // Показываем ошибку только после взаимодействия\n * const showError = touched && invalid;\n * ```\n */\n touched: boolean;\n\n /**\n * Флаг для отображения ошибки.\n * Комбинация touched && invalid - удобный shortcut для UI.\n *\n * @example\n * ```tsx\n * const { shouldShowError, errors } = useFormControl(field);\n *\n * return (\n * <div>\n * <input {...props} />\n * {shouldShowError && (\n * <span className=\"error\">{errors[0]?.message}</span>\n * )}\n * </div>\n * );\n * ```\n */\n shouldShowError: boolean;\n\n /**\n * Пользовательские props для передачи в UI-компоненты.\n * Устанавливаются через {@link FieldNode.updateComponentProps}.\n *\n * @example\n * ```tsx\n * // Установка props\n * field.updateComponentProps({\n * placeholder: 'Enter email...',\n * maxLength: 100,\n * autoComplete: 'email'\n * });\n *\n * // Использование в компоненте\n * const { componentProps, value } = useFormControl(field);\n *\n * return (\n * <input\n * value={value}\n * placeholder={componentProps.placeholder}\n * maxLength={componentProps.maxLength}\n * autoComplete={componentProps.autoComplete}\n * />\n * );\n * ```\n */\n componentProps: Record<string, unknown>;\n\n /**\n * Флаг изменения поля.\n * `true` когда значение поля отличается от начального.\n *\n * Реактивный аналог `control.dirty` — паритет с {@link ArrayControlState.dirty}.\n *\n * @example\n * ```tsx\n * const { dirty } = useFormControl(field);\n *\n * return dirty ? <span className=\"badge\">Изменено</span> : null;\n * ```\n */\n dirty: boolean;\n}","summary":"Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FieldNode}.","description":"Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FieldNode}.\n\nСодержит реактивные данные поля: значение, состояние валидации, флаги взаимодействия\nи пользовательские props для компонентов.","params":[],"example":"Базовое использование\n```tsx\ninterface Props {\ncontrol: FieldNode<string>;\n}\n\nfunction TextField({ control }: Props) {\nconst state = useFormControl(control);\n\nreturn (\n<div>\n<input\nvalue={state.value}\ndisabled={state.disabled}\nonChange={e => control.setValue(e.target.value)}\n/>\n{state.shouldShowError && state.errors[0] && (\n<span className=\"error\">{state.errors[0].message}</span>\n)}\n</div>\n);\n}\n```","examplesCount":1,"related":["useFormControl","ArrayControlState"],"topics":["api-reference","complete-import"],"sourcePath":"src/platforms/react/hooks/types.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldNode","kind":"class","signature":"export class FieldNode<T> extends FormNode<T> {\n // ============================================================================\n // Приватные сигналы\n // ============================================================================ /* … */ }","summary":"FieldNode - узел для отдельного поля формы","description":"FieldNode - узел для отдельного поля формы","params":[],"example":"Пример node-level: конфиг УЗЛА, а не layout-схема M1 (в ней поля `validators` нет).\n```typescript\nconst field = new FieldNode({\n value: '',\n component: InputField,\n validators: [required, email],\n});\n\nfield.setValue('test@mail.com');\nawait field.validate();\nconsole.log(field.valid.value); // true\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","formschema","ui-components","type-safety-recipes"],"sourcePath":"src/form/nodes/field-node.ts","entries":["."],"package":"@reformer/core","usage":7},{"name":"FieldStatus","kind":"type","signature":"export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';","summary":"Статус поля формы","description":"Статус поля формы","params":[],"examplesCount":0,"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"FileLike","kind":"interface","signature":"export interface FileLike {\n name: string;\n size?: number;\n type?: string;\n}","summary":"Минимальный файлоподобный контракт, с которым работают file-валидаторы: нативный `File`, сериализуемый дескриптор загруженного файла и т.п.","description":"Минимальный файлоподобный контракт, с которым работают file-валидаторы:\nнативный `File`, сериализуемый дескриптор загруженного файла и т.п.","params":[],"examplesCount":0,"sourcePath":"src/form/validators/file-utils.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"fileType","kind":"function","signature":"export function fileType<TForm = unknown, TField = unknown>(\n accept: string,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора типа файла.","description":"Фабрика валидатора типа файла.\n\nПроверяет каждый файл против accept-строки в синтаксисе нативного атрибута\n`<input type=\"file\" accept>`: расширения (`.pdf`), точные MIME (`image/png`),\nwildcard-категории (`image/*`), список через запятую. Матчинг регистронезависимый,\nпо `type` и/или расширению из `name` ({@link matchesFileAccept}).\n\nНужен потому, что нативный `accept` — только подсказка пикеру: на drag-and-drop\nи «All files» он не действует. Пустые значения (`null`/`undefined`/`''`/`[]`)\nпропускаются (используйте {@link required} для обязательности).","params":[["accept","- Accept-строка допустимых типов, например `'image/*,.pdf'`"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `accept` и `fileName` (первый нарушивший файл)."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Только изображения и PDF\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { fileType } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/file-type.ts","entries":["./validators"],"package":"@reformer/core","usage":6},{"name":"FormArrayProxy","kind":"type","signature":"export type FormArrayProxy<T extends object> = ArrayNode<T> & {\n /**\n * Безопасный доступ к элементу массива по индексу\n * Возвращает GroupNode с типизированными полями или undefined\n */\n at(index: number): FormProxy<T> | undefined;\n\n /**\n * Итерация по элементам массива с типизированными элементами\n */\n forEach(callback: (item: FormProxy<T>, index: number) => void): void;\n\n /**\n * Маппинг элементов массива с типизированными элементами\n */\n map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];\n};","summary":"Комбинированный тип для ArrayNode с Proxy доступом к элементам Объединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.","description":"Комбинированный тип для ArrayNode с Proxy доступом к элементам\n\nОбъединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.","params":[],"example":"```typescript\ninterface TodoItem {\n title: string;\n completed: boolean;\n}\n\nconst todos: FormArrayProxy<TodoItem> = new ArrayNode(schema);\n\n// Доступ к методам ArrayNode\ntodos.push({ title: 'New todo', completed: false });\ntodos.removeAt(0);\n\n// Доступ к элементам (через Proxy)\ntodos.at(0)?.title.setValue('Updated title');\n\n// Итерация\ntodos.forEach((item, i) => {\n console.log(item.title.value.value);\n});\n```","examplesCount":1,"topics":["api-signatures"],"sourcePath":"src/form/types/form-proxy.ts","entries":["."],"package":"@reformer/core","usage":1},{"name":"FormBehavior","kind":"interface","signature":"export interface FormBehavior<T> {\n /** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */\n __run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;\n}","summary":"Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`.","description":"Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`.","params":[],"examplesCount":0,"sourcePath":"src/form/behaviors/types.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"FormBundleLike","kind":"interface","signature":"export interface FormBundleLike {\n validation?: { controller: FormValidationController };\n}","summary":"Минимум, который хук ожидает от бандла: опциональный контроллер живой валидации.","description":"Минимум, который хук ожидает от бандла: опциональный контроллер живой валидации.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/hooks/use-form-bundle.ts","entries":["."],"package":"@reformer/core"},{"name":"FormControlsProxy","kind":"type","signature":"export type FormControlsProxy<T> = {\n // `-?` снимает опциональность: для каждого поля схемы прокси всегда содержит узел\n // (включая опциональные поля — у них узел существует, опционально лишь значение).\n // Без этого `control.optionalField` имел бы тип `FieldNode<...> | undefined`.\n [K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<infer U>\n ? IsGroupObject<U> extends true\n ? FormArrayProxy<U & object> // Массив объектов → FormArrayProxy\n : FieldNode<T[K]> // Массив примитивов → FieldNode\n : IsGroupObject<NonNullable<T[K]>> extends true\n ? FormProxy<NonNullable<T[K]>> // Обычный объект → FormProxy (рекурсивно!)\n : FieldNode<T[K]>; // Примитивы и спец-объекты (Date/File/Blob) → FieldNode\n};","summary":"Мапит тип модели данных T на правильные типы узлов формы Рекурсивно определяет типы узлов на основе структуры данных: - `T[K] extends Array<infer U>` где U - объект → `FormArrayProxy<U>` - `T[K] extends Array<infer U>` где U - примитив → `FieldNode<T[K]>` (массив как обычное поле) - `T[K] extends ob","description":"Мапит тип модели данных T на правильные типы узлов формы\n\nРекурсивно определяет типы узлов на основе структуры данных:\n- `T[K] extends Array<infer U>` где U - объект → `FormArrayProxy<U>`\n- `T[K] extends Array<infer U>` где U - примитив → `FieldNode<T[K]>` (массив как обычное поле)\n- `T[K] extends object` → `FormProxy<T[K]>` (вложенная форма с типизацией)\n- `T[K]` примитив → `FieldNode<T[K]>` (простое поле)\n\nИспользует NonNullable для правильной обработки опциональных полей","params":[],"examplesCount":0,"sourcePath":"src/form/types/form-proxy.ts","entries":["."],"package":"@reformer/core"},{"name":"FormErrorHandler","kind":"class","signature":"export class FormErrorHandler {\n /**\n * Обработать ошибку согласно заданной стратегии\n *\n * @param error Ошибка для обработки (Error | string | unknown)\n * @param context Контекст ошибки для логирования (например, 'AsyncValidator', 'BehaviorRegistry')\n * @param strategy Стратегия обработки (THROW | LOG | CONVERT)\n * @returns ValidationError если strategy = CONVERT, undefined если strategy = LOG, никогда не возвращается если strategy = THROW\n *\n * @example\n * ```typescript\n * // THROW - пробросить ошибку\n * try {\n * riskyOperation();\n * } catch (error) {\n * FormErrorHandler.handle(error, 'RiskyOperation', ErrorStrategy.THROW);\n * // Этот код никогда не выполнится\n * }\n *\n * // LOG - залогировать и продолжить\n * try {\n * nonCriticalOperation();\n * } catch (error) {\n * FormErrorHandler.handle(error, 'NonCritical', ErrorStrategy.LOG);\n * // Продолжаем выполнение\n * }\n *\n * // CONVERT - конвертировать в ValidationError\n * try {\n * await validator(value);\n * } catch (error) {\n * const validationError = FormErrorHandler.handle(\n * error,\n * 'AsyncValidator',\n * ErrorStrategy.CONVERT\n * );\n * return validationError;\n * }\n * ```\n */ /* … */ }","summary":"Централизованный обработчик ошибок для форм Обеспечивает: - Единообразное логирование ошибок в DEV режиме - Гибкие стратегии обработки (throw/log/convert) - Типобезопасное извлечение сообщений из Error/string/unknown","description":"Централизованный обработчик ошибок для форм\n\nОбеспечивает:\n- Единообразное логирование ошибок в DEV режиме\n- Гибкие стратегии обработки (throw/log/convert)\n- Типобезопасное извлечение сообщений из Error/string/unknown","params":[],"example":"```typescript\n// В async validator (конвертировать в ValidationError)\ntry {\n await validateEmail(value);\n} catch (error) {\n return FormErrorHandler.handle(error, 'EmailValidator', ErrorStrategy.CONVERT);\n}\n\n// В behavior applicator (пробросить критичную ошибку)\ntry {\n applyBehavior(schema);\n} catch (error) {\n FormErrorHandler.handle(error, 'BehaviorApplicator', ErrorStrategy.THROW);\n}\n\n// В validator (залогировать и продолжить)\ntry {\n validator(value);\n} catch (error) {\n FormErrorHandler.handle(error, 'Validator', ErrorStrategy.LOG);\n}\n```","examplesCount":1,"sourcePath":"src/form/validation/error-handler.ts","entries":["."],"package":"@reformer/core"},{"name":"FormModel","kind":"type","signature":"export type FormModel<T> = ModelObject<T> & ModelApi<T>;","summary":"FormModel — под-модель объекта `T`: value-доступ ({@link ModelObject}) + `.$`-сигналы + {@link ModelApi} (get/set/patch/isDirty/reset/signalAt).","description":"FormModel — под-модель объекта `T`: value-доступ ({@link ModelObject}) + `.$`-сигналы + {@link ModelApi}\n(get/set/patch/isDirty/reset/signalAt). Вложенные объекты-группы модели — тоже {@link FormModel}\n(доступны как `model.<group>`), поэтому `model.<group>.$.<field>` эквивалентно `model.$.<group>.<field>`.","params":[],"examplesCount":0,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","formschema","arrays","multi-step","project-structure","nonexistent-api","conditional-fields","reading-values","array-operations","submit-and-reset","async-preload","type-safety-recipes","validation-strategy"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":52},{"name":"FormNode","kind":"class","signature":"export abstract class FormNode<T> {\n // ============================================================================\n // Protected состояние (для Template Method паттерна)\n // ============================================================================\n\n /**\n * Пользователь взаимодействовал с узлом (touched)\n * Protected: наследники могут читать/изменять через методы\n */ /* … */ }","summary":"Абстрактный базовый класс для всех узлов формы.","description":"Абстрактный базовый класс для всех узлов формы.\n\nВсе узлы (поля, группы, массивы) наследуют от этого класса и реализуют\nединый интерфейс для работы с состоянием и валидацией.\n\nTemplate Method паттерн используется для управления состоянием:\nобщие signals (`_touched`, `_dirty`, `_status`) живут в базовом классе,\npubli-методы (`markAsTouched`, `disable`, …) реализованы здесь, а protected\nhooks (`onMarkAsTouched`, `onDisable`, …) переопределяются в наследниках.","params":[],"example":"```typescript\n// FormNode не используется напрямую — экземпляры приходят из getReformerForm.\nimport { getReformerForm, FormNode } from '@reformer/core';\n\nconst form = getReformerForm({ email: '' });\nform.email instanceof FormNode; // true\nform.email.markAsTouched();\n```","examplesCount":1,"sourcePath":"src/form/nodes/form-node.ts","entries":["."],"package":"@reformer/core"},{"name":"FormProxy","kind":"type","signature":"export type FormProxy<T> = GroupNode<T> &\n // Исключаем имена, совпадающие с членами GroupNode: для них `form.<name>` возвращает член узла,\n // а не поле. Оставлять их в полевом пространстве — значит пересекать, напр., ReadonlySignal<FieldStatus>\n // с FieldNode<string> и молча компилировать невозможный тип. Escape-hatch — `form.$` ниже.\n Omit<FormControlsProxy<T>, keyof GroupNode<T>> & {\n /**\n * Escape-hatch пространство имён «controls»: типобезопасный доступ ко ВСЕМ полям формы\n * по имени, включая поля, чьи имена совпадают с членами {@link GroupNode}\n * (`value`/`status`/`id`/`errors`/…), недостижимые через `form.<name>`.\n *\n * @example\n * ```typescript\n * // модель: { status: string; email: string }\n * form.status; // ReadonlySignal<FieldStatus> — агрегат GroupNode (не поле!)\n * form.$.status; // FieldNode<string> — поле пользователя\n * form.$.status.setValue('active');\n * form.$.email; // FieldNode<string> — так же доступны и незатенённые поля\n * ```\n */\n readonly $: FormControlsProxy<T>;\n };","summary":"Комбинированный тип для GroupNode с Proxy доступом к полям Объединяет методы и свойства GroupNode с типизированными полями формы.","description":"Комбинированный тип для GroupNode с Proxy доступом к полям\n\nОбъединяет методы и свойства GroupNode с типизированными полями формы.\nЭто позволяет использовать как API GroupNode, так и прямой доступ к полям.","params":[],"example":"```typescript\ninterface UserForm {\n email: string;\n profile: {\n name: string;\n age: number;\n };\n}\n\nconst form = createForm<UserForm>(schema);\n\n// Доступ к методам GroupNode\nawait form.validate();\nconst values = form.getValue();\nconsole.log(form.valid.value);\n\n// Прямой доступ к полям (через Proxy)\nform.email.setValue('test@mail.com');\nform.profile.name.setValue('John');\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","formschema","arrays","array-operations","async-preload","type-safety-recipes"],"sourcePath":"src/form/types/form-proxy.ts","entries":["."],"package":"@reformer/core","usage":57},{"name":"FormSchema","kind":"type","signature":"export type FormSchema<T> = {\n // Листовые позиции используют NonEmptyFieldConfig: пустой `{}` (частая опечатка) отклоняется\n // на этапе компиляции, но каждое свойство FieldConfig по отдельности остаётся опциональным.\n [K in keyof T]: NonNullable<T[K]> extends string | number | boolean\n ? NonEmptyFieldConfig<T[K]>\n : NonNullable<T[K]> extends Array<infer U>\n ? U extends string | number | boolean\n ? NonEmptyFieldConfig<T[K]>\n : U extends Date | File | Blob | AnyFunction\n ? NonEmptyFieldConfig<T[K]>\n : [FormSchema<U>]\n : NonNullable<T[K]> extends Date | File | Blob | AnyFunction\n ? NonEmptyFieldConfig<T[K]>\n : FormSchema<NonNullable<T[K]>>;\n};","summary":"**Data-shaped** конфиг формы: ключи повторяют структуру данных `T`, значения — {@link FieldConfig} (или вложенный `FormSchema`).","description":"**Data-shaped** конфиг формы: ключи повторяют структуру данных `T`, значения — {@link FieldConfig}\n(или вложенный `FormSchema`). Форма конфига, из которой строится {@link GroupNode}.\n- `T[] -> [FormSchema<T>]` (массив с одним элементом)\n- `object -> FormSchema<T>` (группа)\n- `primitive -> FieldConfig<T>` (поле)\n\nИспользует NonNullable для корректной обработки опциональных полей.\n\n⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/\nконтейнер), передаваемой в `createForm({ model, schema })`. `FormSchema` — это data-shaped конфиг\n(ключи = поля данных), а не узел дерева.","params":[],"example":"```typescript\ninterface Form {\n name: string; // → FieldConfig<string>\n address: { // → FormSchema<Address>\n city: string;\n street: string;\n };\n items?: Array<{ // → [FormSchema<Item>] (опциональный)\n title: string;\n price: number;\n }>;\n}\n\nconst schema: FormSchema<Form> = {\n name: { value: '', component: InputField },\n address: {\n city: { value: '', component: InputField },\n street: { value: '', component: InputField },\n },\n items: [{\n title: { value: '', component: InputField },\n price: { value: 0, component: InputField },\n }],\n};\n```","examplesCount":1,"topics":["api-reference","complete-import","formschema","project-structure"],"sourcePath":"src/form/types/deep-schema.ts","entries":["."],"package":"@reformer/core","usage":1},{"name":"FormSchemaNode","kind":"interface","signature":"export interface FormSchemaNode {\n /**\n * «Ручка» значения поля — маркер узла-поля. Обычно сигнал модели (`model.$.<path>`), но форма\n * зависит от таргета (для массива `model.$.x` — дерево сигналов; в renderer-типах сужается до\n * `Signal`). Движок разбирает узел как поле рантаймом по `value instanceof Signal`.\n */\n value?: unknown;\n /**\n * UI-компонент либо нативный HTML-тег (`'div'`, `'p'`, `'h3'`) для презентационной вёрстки\n * прямо в схеме. Опционален: core-часть работает без UI (значение/валидация) и `component`\n * не интерпретирует — он доезжает до рендерера как есть.\n */\n component?: ElementType;\n /** Props компонента. Также «клапан» для вложенности под-узлов (напр. steps визарда). */\n componentProps?: Record<string, unknown>;\n updateOn?: 'change' | 'blur' | 'submit';\n disabled?: boolean;\n /** Задержка (мс) перед запуском асинхронной валидации. */\n debounce?: number;\n /** Идентификатор узла (для wizard/tabs/renderBehavior). */\n selector?: string;\n /**\n * ⚠️ Рантайм этого поля НЕ ЧИТАЕТ — `renderer-react` берёт testId из `componentProps.testId`\n * (иначе выводит из пути сигнала). Поле оставлено только потому, что `RenderSchemaNode`\n * рендерера объявляет свой одноимённый; пишите `componentProps: { testId: '…' }`.\n */\n testId?: string;\n /**\n * Содержимое узла: под-узлы (даёт контекстную типизацию вложенным литералам — value/validators/when)\n * и текстовые части. Текст (литерал, число, сигнал модели) — такой же ребёнок, как узел: core его\n * не интерпретирует (обход пропускает примитивы и не спускается внутрь сигнала), а рендерер\n * выводит на своём месте в порядке следования.\n */\n children?: readonly (FormSchemaNode | string | number | Signal<any>)[];\n /** Реактивный массив модели (`model.<path>`) — маркер узла-массива (вместе с `item`). */\n array?: SchemaArrayControl;\n /** Схема элемента массива: под-модель элемента → узел поддерева. */\n item?: (itemModel: any) => FormSchemaNode;\n /**\n * Значение нового элемента массива для кнопки «Добавить»: либо готовое значение,\n * либо фабрика `() => value`. Тип не различает варианты (union `unknown | (() => unknown)`\n * схлопывается в `unknown`) — рантайм различает по `typeof initialValue === 'function'`.\n */\n initialValue?: unknown;\n /** Свободная вложенность: record-of-fields и произвольные под-узлы. */\n [key: string]: unknown;\n}","summary":"Узел единой схемы M1 — layout-дерево, обходимое `createForm({ model, schema })` и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).","description":"Узел единой схемы M1 — layout-дерево, обходимое `createForm({ model, schema })`\nи рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).\n\nУзел совмещает несколько ролей (различаются рантаймом по форме):\n - **поле** — несёт `value: Signal` (сигнал модели `model.$.x`) + `component`/`validators`;\n - **массив** — `{ array: model.<path>, item(itemModel) }`;\n - **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;\n - **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).\n\nИндексная сигнатура (`[key: string]: unknown`) отражает свободный рекурсивный обход: под-узлы\nдопустимы под любым ключом. Известные поля типизированы (даёт автокомплит и проверку их типов).","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"FormSubmitter","kind":"class","signature":"export class FormSubmitter<T extends object> {\n /** Внутренний сигнал состояния отправки */ /* … */ }","summary":"FormSubmitter - управляет процессом отправки формы","description":"FormSubmitter - управляет процессом отправки формы","params":[],"example":"```typescript\nconst submitter = new FormSubmitter(form);\n\n// Простой submit\nconst result = await submitter.submit(async (values) => {\n return await api.saveForm(values);\n});\n\n// Проверка состояния\nif (submitter.submitting.value) {\n console.log('Форма отправляется...');\n}\n```","examplesCount":1,"sourcePath":"src/form/form-submitter.ts","entries":["."],"package":"@reformer/core"},{"name":"FormValidation","kind":"interface","signature":"export interface FormValidation<T> {\n /** Полный набор правил — для submit и `validateAll`. Без него собирается из `steps` + `extras`. */\n schema?: ValidationSchema<T>;\n /**\n * Пошаговая валидация визарда: ключ = `selector` шага. `null` означает «шаг без правил» ЯВНО —\n * чтобы опечатка в ключе не выглядела как пустой шаг.\n */\n steps?: Record<string, ValidationSchema<T> | null>;\n /** Cross-field правила, которые проверяются только целиком (в `validateAll`), но не по шагам. */\n extras?: ValidationSchema<T>;\n /** Когда запускать живую валидацию. По умолчанию `'submit'` (реактивно молчит). */\n strategy?: ValidationStrategyKind;\n debounce?: number;\n /** Режим live-фазы для `afterFirstSubmit`. По умолчанию `'change'`. */\n liveAfterSubmit?: 'change' | 'blur';\n}","summary":"Правила валидации формы — ДАННЫЕ (без привязки к рендеру).","description":"Правила валидации формы — ДАННЫЕ (без привязки к рендеру).\n\n⚠️ `schema` и значения `steps` обязаны быть **стабильными ссылками**: отмена устаревших прогонов\nключуется по паре `(model, schema)` через `WeakMap`, поэтому инлайн-стрелка ломает дедупликацию —\nвалидация начнёт возвращать `false` от уже отменённых прогонов. Держите их\nmodule-level-константами (`defineValidationSchema`).","params":[],"examplesCount":0,"sourcePath":"src/form/validation/config.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"FormValidationBundle","kind":"interface","signature":"export interface FormValidationBundle<T> {\n /** Полная схема (стабильная ссылка) — та, по которой идёт `validateAll`. */\n readonly schema: ValidationSchema<T>;\n /** Селекторы шагов в порядке объявления. Пусто, если `steps` не заданы. */\n readonly stepSelectors: readonly string[];\n /** Полный прогон с раскрытием ошибок. Синоним {@link FormValidationBundle.validateAll}. */\n validate(): Promise<boolean>;\n validateAll(): Promise<boolean>;\n /** Прогон правил одного шага: по номеру (1-based, как у визарда) или по селектору. */\n validateStep(step: number | string): Promise<boolean>;\n /** Контроллер живой валидации ШАГА; `null` — у шага нет правил либо стратегия `submit`. */\n createStepController(step: number | string): FormValidationController | null;\n /** Контроллер живой валидации ФОРМЫ. Армится хуком (`useFormBundle`), не фабрикой. */\n readonly controller: FormValidationController;\n /** Идёт ли прогон — реактивный сигнал (для тонкой подписки в UI). */\n readonly validating: ReadonlySignal<boolean>;\n}","summary":"Собранная валидация формы.","description":"Собранная валидация формы. Структурно совместима с `FormWizardConfig` и `StepValidationConfig`\nиз `@reformer/cdk`, поэтому уходит в визард как есть: `config={bundle.validation}` либо\n`patchProps({ form, ...bundle.validation })`.","params":[],"examplesCount":0,"sourcePath":"src/form/validation/config.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"FormValidationController","kind":"interface","signature":"export interface FormValidationController {\n /**\n * Полный прогон схемы с раскрытием ошибок (`touch: true`) — для submit. Также переводит\n * `afterFirstSubmit` в live-фазу. Возвращает `false`, если есть блокирующие ошибки.\n */\n validate(): Promise<boolean>;\n /**\n * Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR.\n *\n * После {@link FormValidationController.dispose} контроллер армируется ЗАНОВО: React монтирует\n * компоненты повторно (StrictMode в разработке, remount по роуту), и «одноразовый» контроллер\n * после такого цикла молча оставался бы без живой валидации.\n */\n start(): () => void;\n /** Снять подписки/таймеры. Идемпотентно; после него `start()` снова армирует стратегию. */\n dispose(): void;\n /** Идёт ли прогон (submit или live) — снапшот. */\n readonly isValidating: boolean;\n /** Реактивный сигнал состояния прогона (для тонкой подписки в UI). */\n readonly validating: ReadonlySignal<boolean>;\n}","summary":"","params":[],"examplesCount":0,"topics":["validation-strategy"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"FormValue","kind":"type","signature":"export type FormValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Date\n | File\n | FormValue[]\n | { [key: string]: FormValue };","summary":"Represents any valid form value type Use this instead of 'any' for form values to maintain type safety","description":"Represents any valid form value type\nUse this instead of 'any' for form values to maintain type safety","params":[],"examplesCount":0,"topics":["common-mistakes","complete-import","arrays","type-safety-recipes"],"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"futureDate","kind":"function","signature":"export function futureDate<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что дата не в прошлом.","description":"Фабрика валидатора, проверяющего что дата не в прошлом.\n\nДата не должна быть раньше сегодняшнего дня (сравнение по нормализованным датам).\nПустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для поля даты (`string | Date`)","example":"Дата не в прошлом\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, futureDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.appointmentDate, [required(), futureDate({ message: 'Дата записи должна быть в будущем' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/future-date.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"getNodeForSignal","kind":"function","signature":"export function getNodeForSignal(signal: Signal<any>): FormNode<any> | undefined","summary":"Найти ноду формы по сигналу модели.","description":"Найти ноду формы по сигналу модели.\n\nИспользуется state-операциями behavior (`enableWhen`/`disableWhen`) и роутингом ошибок\nвалидации в ноды. Возвращает `undefined`, если форма ещё не построена или поле не\nматериализовано в этой форме (например, элемент массива, который строится per-item).","params":[["signal","- Сигнал значения из {@link FormModel}"]],"returns":"Нода формы для этого поля или `undefined`","example":"Роутинг ошибок валидации в ноду поля\n```typescript\nimport { getNodeForSignal } from '@reformer/core';\n\nconst sig = model.$.email;\ngetNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);\n```","examplesCount":1,"related":["registerSignalNode"],"topics":["api-signatures","multi-step","revalidate-when","async-validator-debounce"],"sourcePath":"src/form/signal-node-registry.ts","entries":["."],"package":"@reformer/core","usage":5},{"name":"getNodeType","kind":"function","signature":"export function getNodeType(node: unknown): string","summary":"Получить тип узла как строку (для отладки) Полезно для логирования и отладки","description":"Получить тип узла как строку (для отладки)\n\nПолезно для логирования и отладки","params":[["node","- Узел для проверки"]],"returns":"Строковое название типа узла","example":"```typescript\nconsole.log('Node type:', getNodeType(node)); // \"FieldNode\" | \"GroupNode\" | \"ArrayNode\" | \"FormNode\" | \"Unknown\"\n```","examplesCount":1,"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"getScope","kind":"function","signature":"export function getScope<T>(): BehaviorScope<T>","summary":"Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов).","description":"Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов).","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"GroupNode","kind":"class","signature":"export class GroupNode<T> extends FormNode<T> {\n // ============================================================================\n // Приватные поля\n // ============================================================================ /* … */ }","summary":"GroupNode - узел для группы полей Создаётся из {@link FormSchema} (дерево field-конфигов).","description":"GroupNode - узел для группы полей\n\nСоздаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`\n(M1: `createForm({ model, schema })`); schema-валидация/behavior живут на слое модели\n(`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.","params":[],"example":"```typescript\nconst form = new GroupNode({\n email: { valueSignal: model.$.email, component: InputField },\n password: { valueSignal: model.$.password, component: InputField },\n});\n\n// Прямой доступ к полям через Proxy\nconst proxy = form.getProxy();\nproxy.email.setValue('test@mail.com');\nawait proxy.validate();\nconsole.log(proxy.valid.value);\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import"],"sourcePath":"src/form/nodes/group-node.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"GroupNodeConfig","kind":"interface","signature":"export interface GroupNodeConfig<T> {\n /** Схема структуры формы (поля и их конфигурация) */\n form: FormSchema<T>;\n}","summary":"Конфигурация GroupNode.","description":"Конфигурация GroupNode.\n\nПод M1 группа создаётся из плоской {@link FormSchema} (дерево field-конфигов). Обёртка\n`{ form }` сохранена для совместимости вызова, legacy behavior/validation-схемы удалены (Ф7).","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"integer","kind":"function","signature":"export function integer<TForm = unknown, TField extends number | null | undefined = number>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что число — целое.","description":"Фабрика валидатора, проверяющего что число — целое.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Проверка целого числа\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, integer } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.count, [required(), integer({ message: 'Должно быть целым числом' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/integer.ts","entries":["./validators"],"package":"@reformer/core","usage":3},{"name":"isArrayNode","kind":"function","signature":"export function isArrayNode(value: unknown): value is ArrayNode<object>","summary":"Проверить, является ли значение ArrayNode (массив форм) ArrayNode представляет массив вложенных форм (обычно GroupNode) и имеет array-like методы (push, removeAt, at)","description":"Проверить, является ли значение ArrayNode (массив форм)\n\nArrayNode представляет массив вложенных форм (обычно GroupNode)\nи имеет array-like методы (push, removeAt, at)","params":[["value","- Значение для проверки"]],"returns":"true если value является ArrayNode","example":"```typescript\nif (isArrayNode(node)) {\n node.push(); // OK - добавить элемент\n node.removeAt(0); // OK - удалить элемент\n const item = node.at(0); // OK - получить элемент\n}\n```","examplesCount":1,"topics":["common-mistakes","formschema"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isDate","kind":"function","signature":"export function isDate<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что значение — валидная дата.","description":"Фабрика валидатора, проверяющего что значение — валидная дата.\n\nПринимает `Date` или строку, парсимую в дату. Пустые значения (`''`/`null`/`undefined`)\nпропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для поля даты (`string | Date`)","example":"Проверка валидности даты\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, isDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.eventDate, [required(), isDate({ message: 'Введите корректную дату' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/is-date.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"isDerived","kind":"function","signature":"export function isDerived(signal: Signal<any>): boolean","summary":"Производный ли сигнал (помечен через {@link markDerived}).","description":"Производный ли сигнал (помечен через {@link markDerived}).\n\nЧитается bulk-сеттерами модели/группы, чтобы не затирать вычисляемые (`computeFrom`) поля\nзначениями из payload.","params":[["signal","- Сигнал значения из {@link FormModel}"]],"returns":"`true`, если сигнал помечен производным","example":"```typescript\nimport { isDerived } from '@reformer/core';\n\nif (!isDerived(model.$.total)) {\n model.$.total.value = payload.total; // писать в payload-значение только для «ручных» полей\n}\n```","examplesCount":1,"related":["markDerived"],"sourcePath":"src/model/derived-registry.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"isFieldNode","kind":"function","signature":"export function isFieldNode(value: unknown): value is FieldNode<FormValue>","summary":"Проверить, является ли значение FieldNode (примитивное поле) FieldNode представляет примитивное поле формы (string, number, boolean и т.д.) и имеет валидаторы, но не имеет вложенных полей или элементов массива","description":"Проверить, является ли значение FieldNode (примитивное поле)\n\nFieldNode представляет примитивное поле формы (string, number, boolean и т.д.)\nи имеет валидаторы, но не имеет вложенных полей или элементов массива","params":[["value","- Значение для проверки"]],"returns":"true если value является FieldNode","example":"```typescript\nif (isFieldNode(node)) {\n node.validators; // OK\n node.asyncValidators; // OK\n node.markAsTouched(); // OK\n}\n```","examplesCount":1,"topics":["common-mistakes","formschema"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isFileLike","kind":"function","signature":"export function isFileLike(value: unknown): value is FileLike","summary":"Проверяет, что значение файлоподобно: объект со строковым `name` и опциональными числовым `size` / строковым `type`.","description":"Проверяет, что значение файлоподобно: объект со строковым `name` и опциональными\nчисловым `size` / строковым `type`.","params":[["value","- Проверяемое значение"]],"returns":"`true`, если значение соответствует {@link FileLike}","examplesCount":0,"sourcePath":"src/form/validators/file-utils.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"isFormNode","kind":"function","signature":"export function isFormNode(value: unknown): value is FormNode<FormValue>","summary":"Проверить, является ли значение любым FormNode Проверяет базовые свойства, общие для всех типов узлов","description":"Проверить, является ли значение любым FormNode\n\nПроверяет базовые свойства, общие для всех типов узлов","params":[["value","- Значение для проверки"]],"returns":"true если value является FormNode","example":"```typescript\nif (isFormNode(value)) {\n value.setValue(newValue);\n value.validate();\n}\n```","examplesCount":1,"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isGroupNode","kind":"function","signature":"export function isGroupNode(value: unknown): value is GroupNode<object>","summary":"Проверить, является ли значение GroupNode (объект с вложенными полями) GroupNode представляет объект с вложенными полями формы: имеет навигацию по полям (`getFieldByPath`/`fields`) и НЕ имеет array-методов (`items`/`push`/`removeAt`).","description":"Проверить, является ли значение GroupNode (объект с вложенными полями)\n\nGroupNode представляет объект с вложенными полями формы: имеет навигацию по полям\n(`getFieldByPath`/`fields`) и НЕ имеет array-методов (`items`/`push`/`removeAt`).","params":[["value","- Значение для проверки"]],"returns":"true если value является GroupNode","example":"```typescript\nif (isGroupNode(node)) {\n node.getFieldByPath('user.email'); // OK\n}\n```","examplesCount":1,"topics":["common-mistakes","formschema"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isModelContainerSignal","kind":"function","signature":"export function isModelContainerSignal(value: unknown): boolean","summary":"Узел дерева `model.$` — контейнер (группа/массив), а не лист?","description":"Узел дерева `model.$` — контейнер (группа/массив), а не лист?\n\nКонтейнерный узел структурно совместим с `ReadonlySignal` (`peek`/`value`/`subscribe`), поэтому\nduck-typing «есть `peek` ⇒ это лист» на нём даёт ложное срабатывание. Этот guard — надёжный\nспособ различить: обходчикам дерева нужно спускаться в контейнер, а не читать его целиком.","params":[],"example":"```typescript\nconst isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);\n```","examplesCount":1,"sourcePath":"src/model/model-signals-proxy.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"isNumber","kind":"function","signature":"export function isNumber<TForm = unknown, TField extends number | null | undefined = number>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).","description":"Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).\n\nПустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для\nобязательности). В отличие от других number-валидаторов, **не** пропускает не-числа\nи `NaN` — это его задача.","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Проверка, что значение — число\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, isNumber } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.amount, [required(), isNumber({ message: 'Введите число' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/is-number.ts","entries":["./validators"],"package":"@reformer/core","usage":3},{"name":"markDerived","kind":"function","signature":"export function markDerived(signal: Signal<any>): void","summary":"Пометить сигнал производным (вычисляемым).","description":"Пометить сигнал производным (вычисляемым).\n\nВызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого\nbulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы\nзначение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.\n\nИдемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,\nпишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.","params":[["signal","- Сигнал значения из {@link FormModel}, которым владеет compute"]],"example":"```typescript\nimport { markDerived } from '@reformer/core';\n\n// Помечаем поле total как вычисляемое — bulk-set его не перезапишет\nmarkDerived(model.$.total);\n```","examplesCount":1,"related":["isDerived","unmarkDerived"],"sourcePath":"src/model/derived-registry.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"matchesFileAccept","kind":"function","signature":"export function matchesFileAccept(file: Pick<FileLike, 'name' | 'type'>, accept: string): boolean","summary":"Проверяет файл против accept-паттерна в синтаксисе нативного атрибута `<input type=\"file\" accept>`: список через запятую из расширений (`.pdf`), точных MIME (`image/png`) и wildcard-категорий (`image/*`).","description":"Проверяет файл против accept-паттерна в синтаксисе нативного атрибута\n`<input type=\"file\" accept>`: список через запятую из расширений (`.pdf`),\nточных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.\n\nНужен собственный матчер, потому что `accept` у нативного input — только подсказка\nпикеру: на drag-and-drop и на программное значение он не действует.","params":[["file","- Файлоподобное значение (`name` + опциональный `type`)"],["accept","- Accept-строка; пустая/пробельная строка матчит всё"]],"returns":"`true`, если файл подходит хотя бы под один паттерн","examplesCount":0,"sourcePath":"src/form/validators/file-utils.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"max","kind":"function","signature":"export function max<TForm = unknown, TField extends number | null | undefined = number>(\n maxValue: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора максимального числового значения.","description":"Фабрика валидатора максимального числового значения.\n\nПустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["maxValue","- Максимально допустимое значение (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `max` и `actual`."]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Максимальное значение числового поля\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, max } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.quantity, [max(100)]);\nvalidate(model.$.discount, [required(), max(50, { message: 'Не более 50%' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","form-types","revalidate-when","type-safety-recipes"],"sourcePath":"src/form/validators/max.ts","entries":["./validators"],"package":"@reformer/core","usage":25},{"name":"maxAge","kind":"function","signature":"export function maxAge<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(maxAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора максимального возраста (по дате рождения).","description":"Фабрика валидатора максимального возраста (по дате рождения).\n\nВозраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные\nдаты пропускаются (используйте {@link required} и {@link isDate}).","params":[["maxAgeValue","- Максимально допустимый возраст (в полных годах)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `maxAge` и `currentAge`."]],"returns":"Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)","example":"Максимальный возраст\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxAge } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [maxAge(100, { message: 'Проверьте дату рождения' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","type-safety-recipes"],"sourcePath":"src/form/validators/max-age.ts","entries":["./validators"],"package":"@reformer/core","usage":3},{"name":"maxDate","kind":"function","signature":"export function maxDate<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(maxDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора максимальной даты (включительно).","description":"Фабрика валидатора максимальной даты (включительно).\n\nСравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты\nпропускаются (используйте {@link required} и {@link isDate}).","params":[["maxDateValue","- Максимально допустимая дата (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадает `maxDate`."]],"returns":"Чистый валидатор {@link Validator} для поля даты (`string | Date`)","example":"Максимальная дата\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [maxDate(new Date(), { message: 'Дата не может быть в будущем' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","type-safety-recipes"],"sourcePath":"src/form/validators/max-date.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"maxFiles","kind":"function","signature":"export function maxFiles<TForm = unknown, TField = unknown>(\n max: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора максимального количества файлов.","description":"Фабрика валидатора максимального количества файлов.\n\nРаботает с массивом файлов (одиночный файл считается как 1). Пустые значения\n(`null`/`undefined`/`''`/`[]`) пропускаются (используйте {@link required}\nдля обязательности).","params":[["max","- Максимально допустимое количество файлов (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `maxFiles` и `actualCount`."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Не более трёх вложений\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxFiles } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxFiles(3, { message: 'Максимум 3 файла' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/max-files.ts","entries":["./validators"],"package":"@reformer/core","usage":16},{"name":"maxFileSize","kind":"function","signature":"export function maxFileSize<TForm = unknown, TField = unknown>(\n maxSize: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора максимального размера файла.","description":"Фабрика валидатора максимального размера файла.\n\nРаботает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с\n`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.\nПустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`\n(например, дескриптор загруженного файла без размера) пропускаются\n(используйте {@link required} для обязательности).","params":[["maxSize","- Максимально допустимый размер файла в байтах (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `maxFileSize`, `fileName` и `actualSize`."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Ограничение размера вложений\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/max-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":13},{"name":"maxLength","kind":"function","signature":"export function maxLength<TForm = unknown, TField = unknown>(\n maxLen: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора максимальной длины строки или массива.","description":"Фабрика валидатора максимальной длины строки или массива.\n\nРаботает со строкой или массивом (проверяется `value.length`). Пустые значения\n(`null`/`undefined`/`''`) и значения без числового `length` пропускаются\n(используйте {@link required} для обязательности).","params":[["maxLen","- Максимально допустимая длина (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `maxLength` и `actualLength`."]],"returns":"Чистый валидатор {@link Validator} для строки или массива","example":"Максимальная длина строки\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxLength } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.name, [maxLength(50)]);\nvalidate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","type-safety-recipes"],"sourcePath":"src/form/validators/max-length.ts","entries":["./validators"],"package":"@reformer/core","usage":30},{"name":"maxTotalFileSize","kind":"function","signature":"export function maxTotalFileSize<TForm = unknown, TField = unknown>(\n maxTotal: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора суммарного размера всех файлов.","description":"Фабрика валидатора суммарного размера всех файлов.\n\nСкладывает `size` всех файлов значения (элементы без числового `size` в сумму\nне входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются\n(используйте {@link required} для обязательности).","params":[["maxTotal","- Максимально допустимый суммарный размер в байтах (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `maxTotalFileSize` и `actualTotal`."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Суммарно не более 20 МБ\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { maxTotalFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/max-total-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"min","kind":"function","signature":"export function min<TForm = unknown, TField extends number | null | undefined = number>(\n minValue: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора минимального числового значения.","description":"Фабрика валидатора минимального числового значения.\n\nПустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["minValue","- Минимально допустимое значение (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `min` и `actual`."]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Минимальное значение числового поля\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, min } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.age, [min(18)]);\nvalidate(model.$.quantity, [required(), min(1, { message: 'Минимум 1' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","form-types","arrays","multi-step","nonexistent-api","conditional-fields","type-safety-recipes"],"sourcePath":"src/form/validators/min.ts","entries":["./validators"],"package":"@reformer/core","usage":54},{"name":"minAge","kind":"function","signature":"export function minAge<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(minAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора минимального возраста (по дате рождения).","description":"Фабрика валидатора минимального возраста (по дате рождения).\n\nВозраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные\nдаты пропускаются (используйте {@link required} и {@link isDate}).","params":[["minAgeValue","- Минимально допустимый возраст (в полных годах)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `minAge` и `currentAge`."]],"returns":"Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)","example":"Минимальный возраст\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, minAge } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [required(), minAge(18, { message: 'Вам должно быть не менее 18 лет' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","form-types","type-safety-recipes"],"sourcePath":"src/form/validators/min-age.ts","entries":["./validators"],"package":"@reformer/core","usage":3},{"name":"minDate","kind":"function","signature":"export function minDate<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(minDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора минимальной даты (включительно).","description":"Фабрика валидатора минимальной даты (включительно).\n\nСравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты\nпропускаются (используйте {@link required} и {@link isDate}).","params":[["minDateValue","- Минимально допустимая дата (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадает `minDate`."]],"returns":"Чистый валидатор {@link Validator} для поля даты (`string | Date`)","example":"Минимальная дата\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, minDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.startDate, [required(), minDate(new Date(), { message: 'Дата не раньше сегодня' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","form-types","type-safety-recipes"],"sourcePath":"src/form/validators/min-date.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"minFiles","kind":"function","signature":"export function minFiles<TForm = unknown, TField = unknown>(\n min: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора минимального количества файлов.","description":"Фабрика валидатора минимального количества файлов.\n\nРаботает с массивом файлов (одиночный файл считается как 1). Пустые значения\n(`null`/`undefined`/`''`/`[]`) пропускаются — «нужен хотя бы один файл» выражается\nсвязкой `required()` + `minFiles(n)`, как и у {@link minLength}.","params":[["min","- Минимально допустимое количество файлов (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `minFiles` и `actualCount`."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Не менее двух документов\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, minFiles } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [required(), minFiles(2, { message: 'Приложите минимум 2 документа' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/min-files.ts","entries":["./validators"],"package":"@reformer/core","usage":4},{"name":"minFileSize","kind":"function","signature":"export function minFileSize<TForm = unknown, TField = unknown>(\n minSize: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора минимального размера файла.","description":"Фабрика валидатора минимального размера файла.\n\nРаботает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с\n`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.\nТиповой сценарий — отсев пустых (0-байтовых) файлов: `minFileSize(1)`.\nПустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`\nпропускаются (используйте {@link required} для обязательности).","params":[["minSize","- Минимально допустимый размер файла в байтах (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `minFileSize`, `fileName` и `actualSize`."]],"returns":"Чистый валидатор {@link Validator} для файла или массива файлов","example":"Отсев пустых файлов\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { minFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.documents, [minFileSize(1, { message: 'Файл пустой' })]);\n});\n```","examplesCount":1,"sourcePath":"src/form/validators/min-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"minLength","kind":"function","signature":"export function minLength<TForm = unknown, TField = unknown>(\n minLen: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора минимальной длины строки или массива.","description":"Фабрика валидатора минимальной длины строки или массива.\n\nРаботает со строкой или массивом (проверяется `value.length`). Пустые значения\n(`null`/`undefined`/`''`) и значения без числового `length` пропускаются\n(используйте {@link required} для обязательности).","params":[["minLen","- Минимально допустимая длина (включительно)"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадают `minLength` и `actualLength`."]],"returns":"Чистый валидатор {@link Validator} для строки или массива","example":"Минимальная длина строки\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, minLength } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.name, [minLength(2)]);\nvalidate(model.$.password, [required(), minLength(8, { message: 'Минимум 8 символов' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","form-types","nonexistent-api","submit-and-reset","type-safety-recipes","validation-strategy"],"sourcePath":"src/form/validators/min-length.ts","entries":["./validators"],"package":"@reformer/core","usage":33},{"name":"ModelApi","kind":"interface","signature":"export interface ModelApi<T> {\n /**\n * Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.\n * Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.\n */\n readonly $: ModelGroupSignals<T>;\n /** Снимок значений (без подписки) — для submit. */\n get(): T;\n /**\n * Полная установка значений: принимает объект целиком (все ключи `T`) и записывает их в модель.\n * Производными полями (цели `compute`) владеет compute — их значения из payload игнорируются.\n * Не меняет initial-снимок. Для частичного обновления (только переданные ключи) — {@link ModelApi.patch}.\n */\n set(value: T): void;\n /**\n * Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,\n * отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.\n */\n patch(value: Partial<T>): void;\n /** Отличаются ли текущие значения от initial-снимка (value-diff). */\n isDirty(): boolean;\n /** Сбросить значения к initial-снимку. */\n reset(): void;\n /** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */\n captureInitial(): void;\n /** Резолв строкового пути в сигнал (для error-routing/мостов). */\n signalAt(path: string): PathAwareSignal<unknown> | undefined;\n}","summary":"API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).","description":"API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).\n\n⚠️ Имена методов (`$`/`get`/`set`/`patch`/`isDirty`/`reset`/`signalAt`/`captureInitial`)\nзарезервированы: одноимённое поле формы их затеняет (редкий краевой случай).","params":[],"examplesCount":0,"topics":["submit-and-reset"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelArray","kind":"interface","signature":"export interface ModelArray<U> {\n /**\n * Путь массива в модели (dot-нотация). Предоставляется рантаймом (value-прокси) и требуется\n * рендер-слою для резолва узла массива (напр. `ArrayRenderNode` в `@reformer/renderer-react`).\n */\n readonly __path: string;\n /** Реактивная длина. */\n readonly length: number;\n /** Добавить элемент в конец (значение элемента целиком). */\n push(item: U): void;\n /** Вставить элемент по индексу. */\n insertAt(index: number, item: U): void;\n /** Удалить элемент по индексу. */\n removeAt(index: number): void;\n /** Переместить элемент. */\n move(from: number, to: number): void;\n /** Поменять местами два элемента. */\n swap(a: number, b: number): void;\n /** Очистить массив. */\n clear(): void;\n /** Элемент по индексу: объект → под-модель, массив → {@link ModelArray}, лист → значение. */\n at(\n index: number\n ): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;\n /** Map по элементам (объект → {@link FormModel}, Opaque/примитив → значение, массив → {@link ModelArray}). */\n map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];\n /** Итерация по элементам (см. {@link ModelArrayItem}). */\n forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;\n /** Снимок массива значений (без подписки). */\n toArray(): U[];\n /** Индексный value-доступ. */\n [index: number]: ModelValue<U>;\n}","summary":"Реактивный массив модели.","description":"Реактивный массив модели. Мутации (`push`/`removeAt`/…) меняют длину реактивно;\n`map`/`forEach`/`at` отдают под-модель элемента ({@link FormModel}) для объектных элементов.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","arrays","array-operations"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":1},{"name":"ModelArrayControl","kind":"interface","signature":"export interface ModelArrayControl<TItem extends object> {\n readonly length: number;\n at(index: number): FormModel<TItem> | undefined;\n push(item: TItem): void;\n insertAt(index: number, item: TItem): void;\n removeAt(index: number): void;\n move(from: number, to: number): void;\n swap(a: number, b: number): void;\n clear(): void;\n toArray(): TItem[];\n}","summary":"Минимальный контракт реактивного массива модели, используемый узлом.","description":"Минимальный контракт реактивного массива модели, используемый узлом.","params":[],"examplesCount":0,"sourcePath":"src/form/nodes/model-array-node.ts","entries":["."],"package":"@reformer/core"},{"name":"ModelArrayNode","kind":"class","signature":"export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */ }","summary":"Узел массива, делегирующий данные массиву {@link FormModel} (архитектура M1).","description":"Узел массива, делегирующий данные массиву {@link FormModel} (архитектура M1).\n\nВ отличие от {@link ArrayNode} (владеет элементами сам), `ModelArrayNode` НЕ владеет данными:\nмассив принадлежит модели, а узел держит per-item формы элементов (привязанные к сигналам\nпод-моделей) и синхронизирует их с длиной массива модели. Мутации (`push`/`removeAt`/`move`/…)\nделегируются массиву модели; per-item формы кэшируются по идентичности под-модели, поэтому при\nreorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же\nконтракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).\n\nОбычно создаётся не напрямую, а `createForm({ model, schema })`: когда в схеме встречается узел\nмассива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как\n`ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).","params":[],"example":"Массив как часть формы (через createForm)\n```typescript\nconst model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });\n\nconst rowItem = (item: FormModel<{ name: string; qty: number }>) => ({\nname: { value: item.$.name, component: InputField },\nqty: { value: item.$.qty, component: InputField },\n});\n\nconst form = createForm({\nmodel,\nschema: {\nchildren: [{ array: model.rows, item: rowItem }],\n},\n});\n\nconst rows = form.rows as unknown as ModelArrayNode<{ name: string; qty: number }>;\nrows.push({ name: 'A', qty: 1 }); // мутация уезжает в model.rows\nrows.at(0)?.name.setValue('B'); // правка поля элемента доезжает в под-модель\nrows.length.value; // 1 (реактивная длина)\n```","examplesCount":1,"topics":["api-reference","arrays"],"sourcePath":"src/form/nodes/model-array-node.ts","entries":["."],"package":"@reformer/core","usage":1},{"name":"ModelArraySignals","kind":"type","signature":"export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {\n readonly length: number;\n readonly [index: number]: ModelSignalNode<U>;\n};","summary":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length` и {@link ReadonlySignal} значения массива целиком (реагирует и на правку элемента, и на изменение состава — push/removeAt/move).","description":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`\nи {@link ReadonlySignal} значения массива целиком (реагирует и на правку элемента, и на изменение\nсостава — push/removeAt/move).","params":[],"examplesCount":0,"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":2},{"name":"ModelGroupSignals","kind":"type","signature":"export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;","summary":"Узел-группа дерева `model.$`: доступ к под-сигналам полей ({@link ModelSignals}) И одновременно {@link ReadonlySignal} агрегированного значения группы — `.value`/`.peek()`/`.subscribe()`.","description":"Узел-группа дерева `model.$`: доступ к под-сигналам полей ({@link ModelSignals}) И одновременно\n{@link ReadonlySignal} агрегированного значения группы — `.value`/`.peek()`/`.subscribe()`.\n\n⚠️ Поле формы с именем `value`/`peek`/`subscribe`/`valueOf`/`toString`/`toJSON`/`brand` затеняет\nодноимённое свойство сигнала (редкий краевой случай); `subscribe` при этом продолжает работать.","params":[],"examplesCount":0,"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelObject","kind":"type","signature":"export type ModelObject<T> = {\n [K in keyof T]: ModelValue<T[K]>;\n};","summary":"Карта value-полей объекта (value-половина {@link FormModel}): поля доступны как обычные свойства (чтение реактивно внутри `effect`/`computed`, запись — присваиванием).","description":"Карта value-полей объекта (value-половина {@link FormModel}): поля доступны как обычные свойства\n(чтение реактивно внутри `effect`/`computed`, запись — присваиванием). Вложенные объекты-поля\nрезолвятся в под-модели {@link FormModel} (см. {@link ModelValue}), массивы — в {@link ModelArray}.","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelSignals","kind":"type","signature":"export type ModelSignals<T> = {\n [K in keyof T]: ModelSignalNode<T[K]>;\n};","summary":"Карта под-сигналов объекта в дереве `model.$` (без свойств самого узла — их добавляет {@link ModelGroupSignals}).","description":"Карта под-сигналов объекта в дереве `model.$` (без свойств самого узла — их добавляет\n{@link ModelGroupSignals}).","params":[],"examplesCount":0,"topics":["api-reference","complete-import","type-safety-recipes"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelValue","kind":"type","signature":"export type ModelValue<V> =\n NonNullable<V> extends ReadonlyArray<infer U>\n ? ModelArray<U>\n : NonNullable<V> extends Opaque\n ? V\n : NonNullable<V> extends object\n ? FormModel<NonNullable<V>>\n : V;","summary":"Значение поля в value-доступе модели: - массив → {@link ModelArray} - спец-объект (Date/File/Blob) → как есть - объект → под-модель {@link FormModel} (value-доступ + `.$`-сигналы + API get/set/patch/…); промоутится рантаймом (`makeFormModel`); сигналы идентичны `model.$.<path>` - примитив → значение","description":"Значение поля в value-доступе модели:\n- массив → {@link ModelArray}\n- спец-объект (Date/File/Blob) → как есть\n- объект → под-модель {@link FormModel} (value-доступ + `.$`-сигналы + API get/set/patch/…);\n промоутится рантаймом (`makeFormModel`); сигналы идентичны `model.$.<path>`\n- примитив → значение","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"multipleOf","kind":"function","signature":"export function multipleOf<TForm = unknown, TField extends number | null | undefined = number>(\n divisor: number,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что число кратно заданному.","description":"Фабрика валидатора, проверяющего что число кратно заданному.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["divisor","- Делитель: значение должно быть кратно ему"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадает `multipleOf` (делитель)."]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Проверка кратности\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { multipleOf } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.rating, [multipleOf(0.5, { message: 'Только шаг 0.5' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/multiple-of.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"NodeFactory","kind":"class","signature":"export class NodeFactory {\n /**\n * Создает узел формы на основе конфигурации\n *\n * ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую\n *\n * Автоматически определяет тип узла:\n * - FieldNode: имеет value и component\n * - ArrayNode: массив [schema, ...items] или { schema, initialItems }\n * - GroupNode: объект без value, component, schema\n *\n * @param config Конфигурация узла\n * @returns Экземпляр FieldNode, GroupNode или ArrayNode\n * @throws Error если конфиг не соответствует ни одному типу\n *\n * @example\n * ```typescript\n * const factory = new NodeFactory();\n *\n * Конфиг node-level (не layout-схема M1, где `validators` уже нет).\n * // FieldNode\n * const field = factory.createNode({\n * value: 'test@mail.com',\n * component: InputField,\n * validators: [required, email]\n * });\n *\n * // GroupNode\n * const group = factory.createNode({\n * email: { value: '', component: InputField },\n * password: { value: '', component: InputField }\n * });\n *\n * // ArrayNode (объект)\n * const array = factory.createNode({\n * schema: { title: { value: '', component: InputField } },\n * initialItems: [{ title: 'Item 1' }]\n * });\n *\n * // ArrayNode (массив) - новый формат\n * const array2 = factory.createNode([\n * { title: { value: '', component: InputField } }, // schema\n * { title: 'Item 1' }, // initial item 1\n * { title: 'Item 2' } // initial item 2\n * ]);\n * ```\n */ /* … */ }","summary":"Фабрика для создания узлов формы.","description":"Фабрика для создания узлов формы.\n\nОпределяет тип конфига и создаёт соответствующий узел (FieldNode, GroupNode, ArrayNode).\nИспользуется внутри `getReformerForm`/`group`/`array` — явно вызывать обычно не нужно.","params":[],"example":"```typescript\nimport { NodeFactory } from '@reformer/core';\n\nconst factory = new NodeFactory();\nconst node = factory.createNode({ value: '', component: InputField }); // → FieldNode<string>\n```","examplesCount":1,"sourcePath":"src/form/factories/node-factory.ts","entries":["."],"package":"@reformer/core"},{"name":"nonNegative","kind":"function","signature":"export function nonNegative<TForm = unknown, TField extends number | null | undefined = number>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).","description":"Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Проверка неотрицательности\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { nonNegative } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.balance, [nonNegative({ message: 'Баланс не может быть отрицательным' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/non-negative.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"nonZero","kind":"function","signature":"export function nonZero<TForm = unknown, TField extends number | null | undefined = number>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что число не равно нулю.","description":"Фабрика валидатора, проверяющего что число не равно нулю.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для числового поля","example":"Проверка «не ноль»\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { nonZero } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.divisor, [nonZero({ message: 'Не может быть нулём' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/non-zero.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"onChange","kind":"function","signature":"export function onChange<T>(\n source: ReadonlySignal<T>,\n cb: (value: T, ctx: ChangeContext) => void,\n options?: { immediate?: boolean; debounce?: number }\n): void","summary":"Реакция на изменение поля; `{ debounce, immediate }`.","description":"Реакция на изменение поля; `{ debounce, immediate }`.\n\nКолбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды\n(`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».\n\nДля async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения\nпредыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй\n`signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","arrays","async-watchfield","array-cleanup","extended-mistakes","project-structure","ui-components","nonexistent-api","reading-values","compute-vs-watch","cycle-detection","sync-fields","async-preload","type-safety-recipes","async-validator-debounce","async-options-loading"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":22},{"name":"onDispose","kind":"function","signature":"export function onDispose(cleanup: BehaviorCleanup): void","summary":"Зарегистрировать произвольную отписку в активной схеме.","description":"Зарегистрировать произвольную отписку в активной схеме.","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"pastDate","kind":"function","signature":"export function pastDate<\n TForm = unknown,\n TField extends string | Date | null | undefined = string | Date,\n>(options?: ValidateOptions): Validator<TForm, TField>","summary":"Фабрика валидатора, проверяющего что дата не в будущем.","description":"Фабрика валидатора, проверяющего что дата не в будущем.\n\nДата не должна быть позже сегодняшнего дня (сравнение по нормализованным датам).\nПустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для поля даты (`string | Date`)","example":"Дата не в будущем\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, pastDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [required(), pastDate({ message: 'Дата рождения не может быть в будущем' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validators/past-date.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"PathAwareSignal","kind":"type","signature":"export type PathAwareSignal<T> = Signal<T> & {\n /** Путь поля в модели (dot-нотация). На элементах массива включает индекс. */\n readonly __path: string;\n};","summary":"Сигнал, который знает свой путь в модели (`'personalData.lastName'`).","description":"Сигнал, который знает свой путь в модели (`'personalData.lastName'`).\nИспользуется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","formschema","nonexistent-api","reading-values"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"pattern","kind":"function","signature":"export function pattern<TForm = unknown, TField extends string | null | undefined = string>(\n regex: RegExp,\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора регулярного выражения.","description":"Фабрика валидатора регулярного выражения.\n\nПустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}\nдля обязательности).","params":[["regex","- Регулярное выражение для проверки значения"],["options","- Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически\nпопадает `pattern` (строка-источник regex)."]],"returns":"Чистый валидатор {@link Validator} для строкового поля","example":"Проверка по регулярному выражению\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, pattern } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.name, [pattern(/^[a-zA-Zа-яА-Я]+$/, { message: 'Только буквы' })]);\nvalidate(model.$.phone, [required(), pattern(/^\\+7 \\(\\d{3}\\) \\d{3}-\\d{2}-\\d{2}$/, { message: 'Формат +7 (999) 123-45-67' })]);\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","complete-import","nonexistent-api","copy-from","async-validator-debounce"],"sourcePath":"src/form/validators/pattern.ts","entries":["./validators"],"package":"@reformer/core","usage":21},{"name":"phone","kind":"function","signature":"export function phone<TForm = unknown, TField extends string | null | undefined = string>(\n options?: PhoneValidatorOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора номера телефона.","description":"Фабрика валидатора номера телефона.\n\nПроверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения\n(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки\nавтоматически попадает выбранный `format`."]],"returns":"Чистый валидатор {@link Validator} для строкового поля","example":"Проверка номера телефона\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, phone } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.phone, [required(), phone({ format: 'ru' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","form-types","ui-components","transform-value"],"sourcePath":"src/form/validators/phone.ts","entries":["./validators"],"package":"@reformer/core","usage":20},{"name":"PhoneFormat","kind":"type","signature":"export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';","summary":"Формат проверки номера телефона для валидатора {@link phone}.","description":"Формат проверки номера телефона для валидатора {@link phone}.\n\n- `international` — международный формат E.164 (`+?[1-9]\\d{1,14}`);\n- `ru` — российские номера (`+7`/`7`/`8`, коды `4`/`8`/`9`, с разделителями);\n- `us` — североамериканские номера (NANP);\n- `any` — свободный формат: цифры, скобки и разделители (по умолчанию).","params":[],"examplesCount":0,"topics":["api-signatures"],"sourcePath":"src/form/validators/phone.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"PhoneValidatorOptions","kind":"interface","signature":"export interface PhoneValidatorOptions extends ValidateOptions {\n /** Формат проверки {@link PhoneFormat}. По умолчанию `'any'`. */\n format?: PhoneFormat;\n}","summary":"Опции валидатора {@link phone}.","description":"Опции валидатора {@link phone}. Расширяют {@link ValidateOptions} (`message`, `params`)\nвыбором формата номера.","params":[],"examplesCount":0,"sourcePath":"src/form/validators/phone.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"registerSignalNode","kind":"function","signature":"export function registerSignalNode(signal: Signal<any>, node: FormNode<any>): void","summary":"Связать сигнал модели с его нодой формы.","description":"Связать сигнал модели с его нодой формы.\n\nВызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого\nлистового поля. Прикладной код обычно этот реестр не заполняет напрямую.","params":[["signal","- Сигнал значения из {@link FormModel} (ручка поля, `model.$.path`)"],["node","- Нода формы, отвечающая за это поле"]],"example":"Привязка листовых полей при построении формы\n```typescript\nimport { registerSignalNode } from '@reformer/core';\n\nconst sig = model.signalAt('profile.email');\nconst node = group.getFieldByPath('profile.email');\nif (sig && node) registerSignalNode(sig, node);\n```","examplesCount":1,"related":["getNodeForSignal"],"sourcePath":"src/form/signal-node-registry.ts","entries":["."],"package":"@reformer/core"},{"name":"required","kind":"function","signature":"export function required<TForm = unknown, TField = unknown>(\n options?: ValidateOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора обязательного поля.","description":"Фабрика валидатора обязательного поля.\n\nВозвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.\n\nПустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —\nобязательный multi-select / FormArray без выбранных элементов).\nДля boolean полей требуется значение `true`.","params":[["options","- Опции валидатора ({@link ValidateOptions}): `message`, `params`"]],"returns":"Чистый валидатор {@link Validator} для поля схемы","example":"Обязательные поля в схеме формы\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.email, [required()]);\nvalidate(model.$.phone, [required({ message: 'Укажите номер телефона' })]);\nvalidate(model.$.agreeToTerms, [required({ message: 'Необходимо принять условия' })]);\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","formschema","arrays","multi-step","extended-mistakes","nonexistent-api","conditional-fields","reset-when","submit-and-reset","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/validators/required.ts","entries":["./validators"],"package":"@reformer/core","usage":126},{"name":"resetWhen","kind":"function","signature":"export function resetWhen<T>(\n target: Signal<T>,\n condition: () => boolean,\n options?: { resetValue?: T }\n): BehaviorCleanup","summary":"Сброс значения поля к `resetValue` (по умолчанию `null`), когда `condition` истинно.","description":"Сброс значения поля к `resetValue` (по умолчанию `null`), когда `condition` истинно.","params":[],"example":"```typescript\nresetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue: '' });\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","conditional-fields","cycle-detection","copy-from","reset-when"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":4},{"name":"revalidateWhen","kind":"function","signature":"export function revalidateWhen(\n deps: ReadonlySignal<unknown>[],\n revalidate: () => void\n): BehaviorCleanup","summary":"Вызывает `revalidate()` при изменении зависимостей (не на инициализации).","description":"Вызывает `revalidate()` при изменении зависимостей (не на инициализации). Валидация on-demand\n(`validateModel` из `@reformer/core/validation`), поэтому ревалидация выражается явным колбэком.","params":[],"example":"```typescript\nrevalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","complete-import","extended-mistakes","nonexistent-api","revalidate-when","submit-and-reset","async-validator-debounce","validation-strategy"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":5},{"name":"Rule","kind":"type","signature":"export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;","summary":"Синхронное правило поля типа `TField`.","description":"Синхронное правило поля типа `TField`. Проверяется ТОЛЬКО значение (`value`).\n\nПозиционные `scope`/`root` помечены `never` намеренно: так встроенные value-only фабрики\n(`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`\nОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля\n(`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).\nНа вызове раннер приводит правило к callable и передаёт `(value, model, model)`.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","project-structure","nonexistent-api","compute-vs-watch","type-safety-recipes"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core","usage":82},{"name":"runOutsideEffect","kind":"function","signature":"export function runOutsideEffect(fn: () => void | Promise<void>): () => void","summary":"Выполняет функцию вне контекста effect (отложенная запись на микротаск).","description":"Выполняет функцию вне контекста effect (отложенная запись на микротаск).\n\nСбои fn (синхронный throw и async-rejection) маршрутизируются dev-логгером (logDeferError), а не\nвсплывают неперехваченными. Возвращает отменитель: вызов до срабатывания микротаска отменяет запись\n(liveness-охрана — не писать против снесённого узла при dispose в том же тике).","params":[["fn","- Функция для выполнения"]],"returns":"Отменитель ещё не выполненной записи (no-op, если запись уже выполнена)","example":"```typescript\neffect(() => {\n const value = signal.value;\n const cancel = runOutsideEffect(() => {\n otherSignal.value = transform(value);\n });\n onDispose(cancel); // отменить ожидающую запись, если узел снесут в этом же тике\n});\n```","examplesCount":1,"topics":["cycle-detection","copy-from","sync-fields","transform-value"],"sourcePath":"src/model/safe-effect.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"safeCallback","kind":"function","signature":"export function safeCallback<TArgs extends unknown[]>(\n callback: (...args: TArgs) => void | Promise<void>\n): (...args: TArgs) => void","summary":"Создает callback, который выполняется вне контекста effect Откладывает вызов через {@link runOutsideEffect} — то есть получает маршрутизацию ошибок и обработку async-rejection «бесплатно».","description":"Создает callback, который выполняется вне контекста effect\n\nОткладывает вызов через {@link runOutsideEffect} — то есть получает маршрутизацию ошибок и\nобработку async-rejection «бесплатно».","params":[["callback","- Функция для выполнения"]],"returns":"Обёрнутая функция, безопасная для вызова внутри effect","example":"```typescript\n// Вместо:\neffect(() => {\n queueMicrotask(() => {\n callback(value, context);\n });\n});\n\n// Используем:\neffect(() => {\n safeCallback(callback)(value, context);\n});\n```","examplesCount":1,"sourcePath":"src/model/safe-effect.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"safeDebouncedCallback","kind":"function","signature":"export function safeDebouncedCallback(\n callback: () => void | Promise<void>,\n withDebounce: (fn: () => void) => void\n): () => void","summary":"Создает версию callback с поддержкой debounce, безопасную для effect `withDebounce` уже откладывает вызов (таймер) — этого достаточно для выхода из effect-контекста, поэтому дополнительного `queueMicrotask` нет (устранён двойной defer).","description":"Создает версию callback с поддержкой debounce, безопасную для effect\n\n`withDebounce` уже откладывает вызов (таймер) — этого достаточно для выхода из effect-контекста,\nпоэтому дополнительного `queueMicrotask` нет (устранён двойной defer). Сбои callback (в т.ч.\nasync-rejection) маршрутизируются dev-логгером (logDeferError).","params":[["callback","- Функция для выполнения"],["withDebounce","- Функция debounce обёртки из BehaviorRegistry"]],"returns":"Обёрнутая функция","example":"```typescript\nreturn effect(() => {\n const value = node.value.value;\n safeDebouncedCallback(\n () => callback(value, context),\n withDebounce\n )();\n});\n```","examplesCount":1,"sourcePath":"src/model/safe-effect.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"SchemaArrayControl","kind":"interface","signature":"export interface SchemaArrayControl {\n /** Путь массива в модели (dot-нотация) — нужен для резолва узла массива. */\n readonly __path: string;\n /** Реактивная длина. */\n readonly length: number;\n at(index: number): unknown;\n push(item: unknown): void;\n removeAt(index: number): void;\n}","summary":"Минимальный контракт реактивного массива модели ({@link FormSchemaNode.array}).","description":"Минимальный контракт реактивного массива модели ({@link FormSchemaNode.array}).\nСовпадает по форме с рантайм-фасадом `model.<array>` (см. `ModelArray`); рендерерский\n`RenderModelArrayControl` — его расширение (добавляет `move`).","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SetValueOptions","kind":"interface","signature":"export interface SetValueOptions {\n /** Не вызывать событие изменения (не триггерить валидацию) */\n emitEvent?: boolean;\n // onlySelf удалён в 7.0: опция объявлялась, но не была реализована ни одной из четырёх\n // реализаций setValue — передача значения ни на что не влияла.\n}","summary":"Опции для setValue","description":"Опции для setValue","params":[],"examplesCount":0,"sourcePath":"src/form/nodes/form-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SubmitOptions","kind":"interface","signature":"export interface SubmitOptions {\n /** Пропустить валидацию перед submit */\n skipValidation?: boolean;\n /** Пропустить markAsTouched перед submit */\n skipTouch?: boolean;\n}","summary":"Опции для submit","description":"Опции для submit","params":[],"examplesCount":0,"sourcePath":"src/form/form-submitter.ts","entries":["."],"package":"@reformer/core"},{"name":"SubmitResult","kind":"interface","signature":"export interface SubmitResult<R> {\n /** Успешно ли выполнен submit */\n success: boolean;\n /** Результат от onSubmit callback */\n data: R | null;\n /** Ошибка, если submit не удался */\n error?: Error;\n}","summary":"Результат submit","description":"Результат submit","params":[],"examplesCount":0,"sourcePath":"src/form/form-submitter.ts","entries":["."],"package":"@reformer/core"},{"name":"SubmittableForm","kind":"interface","signature":"export interface SubmittableForm<T extends object> {\n /** Пометить все поля как touched */\n markAsTouched(): void;\n /** Валидировать форму */\n validate(): Promise<boolean>;\n /** Получить значения формы */\n getValue(): T;\n}","summary":"Интерфейс формы для FormSubmitter Минимальный контракт для работы с любой формой","description":"Интерфейс формы для FormSubmitter\nМинимальный контракт для работы с любой формой","params":[],"examplesCount":0,"sourcePath":"src/form/form-submitter.ts","entries":["."],"package":"@reformer/core"},{"name":"SubscriptionManager","kind":"class","signature":"export class SubscriptionManager {\n /**\n * Хранилище подписок\n * Ключ: уникальный идентификатор подписки\n * Значение: функция отписки (dispose)\n */ /* … */ }","summary":"Менеджер подписок для FormNode Централизует управление effect-подписками в узлах формы, предотвращает утечки памяти и упрощает отладку.","description":"Менеджер подписок для FormNode\n\nЦентрализует управление effect-подписками в узлах формы,\nпредотвращает утечки памяти и упрощает отладку.\n\nКаждая подписка имеет уникальный ключ, что позволяет:\n- Отписываться от конкретной подписки по ключу\n- Автоматически заменять существующие подписки\n- Отслеживать количество активных подписок (для отладки)","params":[],"example":"```typescript\nclass FieldNode {\n private subscriptions = new SubscriptionManager();\n\n watch(callback: Function) {\n const dispose = effect(() => callback(this.value.value));\n return this.subscriptions.add('watch', dispose);\n }\n\n dispose() {\n this.subscriptions.clear();\n }\n}\n```","examplesCount":1,"sourcePath":"src/form/nodes/subscription-manager.ts","entries":["."],"package":"@reformer/core"},{"name":"syncFields","kind":"function","signature":"export function syncFields<T>(\n a: Signal<T>,\n b: Signal<T>,\n options?: { transform?: (value: T) => T }\n): BehaviorCleanup","summary":"Двусторонняя синхронизация двух полей (опционально с трансформом `a → b`).","description":"Двусторонняя синхронизация двух полей (опционально с трансформом `a → b`).\nСходимость обеспечивается peek-guard'ом; флаг предотвращает лишние круги.","params":[],"example":"```typescript\nsyncFields(model.$.field1, model.$.field2);\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","cycle-detection","copy-from","sync-fields","transform-value"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":4},{"name":"toFileArray","kind":"function","signature":"export function toFileArray(value: unknown): FileLike[] | null","summary":"Нормализует значение file-поля в массив файлоподобных элементов.","description":"Нормализует значение file-поля в массив файлоподобных элементов.\n\nОдиночный {@link FileLike} оборачивается в массив; из массива отбираются только\nфайлоподобные элементы. Для прочих значений — `null` (валидатор пропускает).","params":[["value","- Значение поля (`FileLike`, `FileLike[]` или что-то ещё)"]],"returns":"Массив файлоподобных элементов или `null`","examplesCount":0,"sourcePath":"src/form/validators/file-utils.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"transformValue","kind":"function","signature":"export function transformValue<T>(\n target: Signal<T>,\n transformer: (value: T) => T\n): BehaviorCleanup","summary":"Трансформация значения поля (идемпотентная): при изменении пишет `transformer(value)` обратно.","description":"Трансформация значения поля (идемпотентная): при изменении пишет `transformer(value)` обратно.\nЗапись отложена (`runOutsideEffect`) во избежание «Cycle detected» (эффект читает и пишет один сигнал).","params":[],"example":"```typescript\ntransformValue(model.$.promoCode, (v) => (v ?? '').toUpperCase());\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","extended-mistakes","nonexistent-api","cycle-detection","sync-fields","transform-value"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model","./behaviors"],"package":"@reformer/core","usage":6},{"name":"UnknownRecord","kind":"type","signature":"export type UnknownRecord = Record<string, unknown>;","summary":"Тип для Record с unknown значениями Используется вместо инлайнового `Record<string, unknown>`","description":"Тип для Record с unknown значениями\nИспользуется вместо инлайнового `Record<string, unknown>`","params":[],"examplesCount":0,"sourcePath":"src/form/types/index.ts","entries":["."],"package":"@reformer/core"},{"name":"unmarkDerived","kind":"function","signature":"export function unmarkDerived(signal: Signal<any>): void","summary":"Снять пометку производного сигнала (обратная операция к {@link markDerived}).","description":"Снять пометку производного сигнала (обратная операция к {@link markDerived}).\n\nВызывается из очистки (`onDispose`) оператора `compute`/`computeFrom`, когда поведение снимается,\nно модель/сигнал продолжают жить (динамическая перекоммутация: удалить под-схему → сохранить модель\n→ bulk-set). Без этого сигнал остаётся помеченным навсегда, и bulk-сеттеры навсегда пропускают\nполе, которое больше ничем не вычисляется.\n\nУчитывает счётчик ссылок: пометка снимается только когда снят последний владелец. Вызов на\nнепомеченном сигнале — безопасный no-op.","params":[["signal","- Сигнал значения из {@link FormModel}, ранее помеченный {@link markDerived}"]],"example":"```typescript\nimport { markDerived, unmarkDerived } from '@reformer/core';\n\nmarkDerived(model.$.total);\nonDispose(() => unmarkDerived(model.$.total)); // при снятии compute снова разрешаем bulk-set\n```","examplesCount":1,"related":["markDerived"],"sourcePath":"src/model/derived-registry.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"url","kind":"function","signature":"export function url<TForm = unknown, TField extends string | null | undefined = string>(\n options?: UrlValidatorOptions\n): Validator<TForm, TField>","summary":"Фабрика валидатора URL.","description":"Фабрика валидатора URL.\n\nПустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}\nдля обязательности). При `requireProtocol` протокол обязателен; `allowedProtocols`\nдополнительно ограничивает набор допустимых протоколов.","params":[["options","- Опции валидатора {@link UrlValidatorOptions}"]],"returns":"Чистый валидатор {@link Validator} для строкового поля","example":"Проверка URL\n```typescript\nimport { defineValidationSchema, validate } from '@reformer/core/validation';\nimport { required, url } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidationSchema<MyForm>(({ model }) => {\nvalidate(model.$.website, [required(), url({ message: 'Введите корректный URL' })]);\n// Требовать протокол и ограничить схему только https:\nvalidate(model.$.homepage, [url({ requireProtocol: true, allowedProtocols: ['https'] })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","async-validator-debounce"],"sourcePath":"src/form/validators/url.ts","entries":["./validators"],"package":"@reformer/core","usage":3},{"name":"UrlValidatorOptions","kind":"interface","signature":"export interface UrlValidatorOptions extends ValidateOptions {\n /** Требовать наличие протокола (`http://` или `https://`). По умолчанию `false`. */\n requireProtocol?: boolean;\n /**\n * Список разрешённых протоколов (без `://`), например `['https']`. Если задан,\n * значение с иным протоколом даёт ошибку с кодом `url_protocol`.\n */\n allowedProtocols?: string[];\n}","summary":"Опции валидатора {@link url}.","description":"Опции валидатора {@link url}. Расширяют {@link ValidateOptions} (`message`, `params`)\nнастройками проверки протокола.","params":[],"examplesCount":0,"sourcePath":"src/form/validators/url.ts","entries":["./validators"],"package":"@reformer/core"},{"name":"useArrayLength","kind":"function","signature":"export function useArrayLength<T extends object>(control: ArrayNode<T>): number","summary":"React-хук для подписки только на длину массива.","description":"React-хук для подписки только на длину массива.\n\nОптимизированная версия {@link useFormControl} для ArrayNode, которая\nподписывается только на сигнал `length`. Компонент не будет ре-рендериться\nпри изменении значений вложенных полей.","params":[["control","- ArrayNode для подписки"]],"returns":"Текущая длина массива","example":"```tsx\nfunction ArrayRenderer({ arrayNode }) {\n const length = useArrayLength(arrayNode);\n\n return (\n <div>\n {arrayNode.map((item, index) => (\n <ItemRenderer key={item.id} item={item} />\n ))}\n </div>\n );\n}\n```","examplesCount":1,"topics":["api-reference","complete-import","arrays","reading-values","array-operations"],"sourcePath":"src/platforms/react/hooks/useArrayLength.ts","entries":["."],"package":"@reformer/core"},{"name":"useFormBundle","kind":"function","signature":"export function useFormBundle<B extends FormBundleLike>(factory: () => B): B","summary":"Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте (не при SSR — там эффекты не выполняются).","description":"Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте\n(не при SSR — там эффекты не выполняются).","params":[["factory","- Фабрика бандла, обычно `() => createCoreForm({…})`."]],"returns":"Стабильный бандл.","example":"```tsx\nconst credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCreditModel() }));\n```","examplesCount":1,"topics":["quick-start","project-structure"],"sourcePath":"src/platforms/react/hooks/use-form-bundle.ts","entries":["."],"package":"@reformer/core","usage":16},{"name":"useFormControl","kind":"function","signature":"export function useFormControl(\n control: FieldNode<FormValue> | ArrayNode<object> | undefined\n): FieldControlState<FormValue> | ArrayControlState<object>","summary":"React-хук для подписки на состояние формы (FieldNode или ArrayNode).","description":"React-хук для подписки на состояние формы (FieldNode или ArrayNode).\n\nОбеспечивает реактивную связь между состоянием формы и React-компонентами.\nИспользует `useSyncExternalStore` для оптимальной интеграции с React 18+\nи Concurrent Mode.\n\n## Основные возможности\n\n- **Автоматическая подписка** на все сигналы контрола\n- **Оптимизация ре-рендеров** - компонент обновляется только при реальных изменениях\n- **Поддержка SSR** через `useSyncExternalStore`\n- **Типобезопасность** - возвращаемый тип зависит от типа контрола\n\n## Когда использовать\n\nИспользуйте `useFormControl` когда компоненту нужен доступ к нескольким\nсвойствам состояния (value, errors, touched и т.д.).\n\nДля подписки только на значение используйте {@link useFormControlValue} -\nэто предотвратит лишние ре-рендеры при изменении других свойств.","params":[["control","- FieldNode, ArrayNode или undefined"]],"returns":"Объект состояния {@link FieldControlState} или {@link ArrayControlState}","example":"Текстовое поле с валидацией\n```tsx\nimport { useFormControl } from '@reformer/core';\nimport type { FieldNode } from '@reformer/core';\n\ninterface TextFieldProps {\ncontrol: FieldNode<string>;\nlabel: string;\n}\n\nfunction TextField({ control, label }: TextFieldProps) {\nconst {\nvalue,\ndisabled,\nshouldShowError,\nerrors,\npending\n} = useFormControl(control);\n\nreturn (\n<div className=\"field\">\n<label>{label}</label>\n\n<div className=\"input-wrapper\">\n<input\n type=\"text\"\n value={value}\n disabled={disabled}\n onChange={e => control.setValue(e.target.value)}\n onBlur={() => control.markAsTouched()}\n aria-invalid={shouldShowError}\n/>\n{pending && <Spinner />}\n</div>\n\n{shouldShowError && errors[0] && (\n<span className=\"error\" role=\"alert\">\n {errors[0].message}\n</span>\n)}\n</div>\n);\n}\n```","examplesCount":5,"related":["useFormControlValue","FieldControlState","ArrayControlState"],"topics":["api-reference","quick-start","common-patterns","common-mistakes","complete-import","ui-components","nonexistent-api","reading-values","async-preload","type-safety-recipes","validation-strategy"],"sourcePath":"src/platforms/react/hooks/useFormControl.ts","entries":["."],"package":"@reformer/core","usage":8},{"name":"useFormControlValue","kind":"function","signature":"export function useFormControlValue<T extends FormValue>(control: FieldNode<T>): T","summary":"React-хук для подписки только на значение поля.","description":"React-хук для подписки только на значение поля.\n\nОптимизированная версия {@link useFormControl}, которая подписывается\nтолько на сигнал `value`. Компонент не будет ре-рендериться при изменении\n`errors`, `touched`, `valid` и других свойств состояния.\n\n## Когда использовать\n\n- **Условный рендеринг** на основе значения другого поля\n- **Вычисляемые значения** зависящие от значения поля\n- **Read-only отображение** значения без интерактивности\n- **Оптимизация производительности** когда не нужны другие свойства состояния\n\n## Когда НЕ использовать\n\nЕсли компоненту нужны `errors`, `touched`, `disabled` или другие свойства -\nиспользуйте {@link useFormControl}. Множественные подписки на один контрол\nчерез разные хуки менее эффективны, чем одна подписка через `useFormControl`.","params":[["control","- FieldNode для подписки на значение"]],"returns":"Текущее значение поля","example":"Условный рендеринг секции\n```tsx\nimport { useFormControlValue } from '@reformer/core';\n\ninterface FormFields {\nhasShipping: FieldNode<boolean>;\nshippingAddress: GroupNode<AddressFields>;\n}\n\nfunction ShippingSection({ form }: { form: FormFields }) {\n// Подписка только на значение checkbox\nconst hasShipping = useFormControlValue(form.hasShipping);\n\nif (!hasShipping) {\nreturn null;\n}\n\nreturn (\n<div className=\"shipping-section\">\n<h3>Shipping Address</h3>\n<AddressForm control={form.shippingAddress} />\n</div>\n);\n}\n```","examplesCount":5,"related":["useFormControl","FieldNode"],"topics":["api-reference","common-patterns","common-mistakes","troubleshooting","complete-import","nonexistent-api","conditional-fields","reading-values"],"sourcePath":"src/platforms/react/hooks/useFormControlValue.ts","entries":["."],"package":"@reformer/core","usage":29},{"name":"useFormValidation","kind":"function","signature":"export function useFormValidation<T>(args: UseFormValidationArgs<T>): UseFormValidationResult","summary":"Декларативный выбор стратегии валидации формы: одна точка вместо ручной разводки `validateModel`/`revalidateWhen`.","description":"Декларативный выбор стратегии валидации формы: одна точка вместо ручной разводки\n`validateModel`/`revalidateWhen`. Собирает стабильный {@link createFormValidation}-контроллер,\nармит реактивную стратегию на клиенте (`useEffect` → SSR-safe) и отдаёт `submit`/`isValidating`.\n\nСхема-валидации — **стабильная ссылка** (обязанность вызывающего): контроллер мемоизируется по\n`[model, schema, strategy, debounce, liveAfterSubmit]`.","params":[],"example":"```tsx\nconst validation = defineValidationSchema<Form>(({ model }) => { ... }); // module-level\n\nfunction MyForm() {\n const { submit, isValidating } = useFormValidation({\n model, schema: validation, strategy: 'afterFirstSubmit', debounce: 300,\n });\n const onSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (await submit()) await api.save(model.get());\n };\n return <form onSubmit={onSubmit}>{ ... }<button disabled={isValidating}>Отправить</button></form>;\n}\n```","examplesCount":1,"topics":["validation-strategy"],"sourcePath":"src/platforms/react/hooks/use-form-validation.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"UseFormValidationArgs","kind":"interface","signature":"export interface UseFormValidationArgs<T> {\n /** Модель данных формы. */\n model: FormModel<T>;\n /** Схема валидации. **Стабильная ссылка** (module-level `const` / `useMemo`) — иначе ломается дедуп раннера. */\n schema: ValidationSchema<T>;\n /** Стратегия запуска. Default `'submit'`. */\n strategy?: ValidationStrategyKind;\n /** Debounce (мс) для live-фаз (`change` / live-часть `afterFirstSubmit`). */\n debounce?: number;\n /** Режим live-фазы для `afterFirstSubmit`. Default `'change'`. */\n liveAfterSubmit?: 'change' | 'blur';\n}","summary":"","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/hooks/use-form-validation.ts","entries":["."],"package":"@reformer/core"},{"name":"UseFormValidationResult","kind":"interface","signature":"export interface UseFormValidationResult {\n /** Полный прогон + раскрытие ошибок (`touch:true`); для `onSubmit`. Стабильная ссылка. */\n submit: () => Promise<boolean>;\n /** Алиас `submit` для не-submit сценариев. */\n validate: () => Promise<boolean>;\n /** Идёт ли прогон (submit или live) — для блокировки кнопки. */\n isValidating: boolean;\n}","summary":"","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/hooks/use-form-validation.ts","entries":["."],"package":"@reformer/core"},{"name":"validate","kind":"function","signature":"export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void","summary":"Синхронные правила поля.","description":"Синхронные правила поля.","params":[],"example":"```ts\nvalidate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","complete-import","formschema","arrays","multi-step","extended-mistakes","nonexistent-api","conditional-fields","submit-and-reset","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":145},{"name":"validateAsync","kind":"function","signature":"export function validateAsync<TField>(\n sig: PathAwareSignal<TField>,\n rules: AsyncRule<TField>[]\n): void","summary":"Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).","description":"Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).\nРаннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.","params":[],"example":"```ts\nvalidateAsync(model.$.username, [async (v, { signal }) => {\n const r = await fetch(`/api/free?u=${v}`, { signal });\n return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };\n}]);\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","extended-mistakes","nonexistent-api","submit-and-reset","async-validator-debounce"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":12},{"name":"validateModel","kind":"function","signature":"export async function validateModel<T>(\n model: FormModel<T>,\n schema: ValidationSchema<T>,\n options?: { touch?: boolean }\n): Promise<boolean>","summary":"Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма).","description":"Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма). Открывает ambient-окно на время\nсинхронного прогона `schema`, дожидается async-правил, разносит ошибки по нодам формы и гасит\nполя, ставшие валидными. Возвращает `true`, если нет блокирующих ошибок (`severity:'warning'` не блокирует).\n\nГашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)` не мешают\nдруг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema)) отменяется через `AbortSignal`\nи возвращает `false` (**fail-closed** — отменённому результату нельзя доверять для submit).\n\n⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности `schema`.\nИнлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ прогон без дедупликации —\nдержите схемы в `const` / `defineValidationSchema`.\n\n`options.touch` (§6): по завершении помечает `touched` ИМЕННО поля, которые схема проверяла\n(не всё поддерево). Ошибки становятся видимыми (`shouldShowError = invalid && (touched||dirty)`)\nбез ручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается\n«тронутым» до ввода. Валидные проверенные поля тоже метятся — это безвредно (ошибка не покажется).","params":[["model","- Модель данных формы."],["schema","- Схема валидации (стабильная ссылка)."],["options","- `{ touch }` — пометить провалидированные поля `touched` для показа ошибок."]],"example":"```ts\nconst ok = await validateModel(model, step2Validation); // один шаг\nconst all = await validateModel(model, formValidation); // вся форма\nconst stepOk = await validateModel(model, step2Validation, { touch: true }); // + показать ошибки шага\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","formschema","arrays","multi-step","extended-mistakes","project-structure","nonexistent-api","conditional-fields","revalidate-when","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"package":"@reformer/core","usage":28},{"name":"ValidateOptions","kind":"interface","signature":"export interface ValidateOptions {\n /** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст\n * резолвится из `code` (см. резолвер сообщений в `@reformer/cdk`). */\n message?: string;\n /** Параметры ошибки (подстановка в шаблон сообщения / i18n). */\n params?: Record<string, FormValue>;\n}","summary":"Опции валидатора-фабрики (`required()`/`pattern()`/…).","description":"Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)\nаргументом в фабрику и попадают в возвращаемую {@link ValidationError}.","params":[],"examplesCount":0,"sourcePath":"src/form/types/validation-schema.ts","entries":["."],"package":"@reformer/core"},{"name":"validateWhen","kind":"function","signature":"export function validateWhen(cond: () => boolean, cb: () => void): void","summary":"Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля ГАСЯТСЯ (пустой bucket → `setErrors([])`).","description":"Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля\nГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","extended-mistakes","nonexistent-api","conditional-fields","submit-and-reset"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":8},{"name":"ValidationError","kind":"interface","signature":"export interface ValidationError {\n code: string;\n message: string;\n params?: Record<string, FormValue>;\n /** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */\n severity?: 'error' | 'warning';\n}","summary":"Ошибка валидации","description":"Ошибка валидации","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","extended-mistakes","nonexistent-api","conditional-fields","array-operations","revalidate-when","type-safety-recipes","async-validator-debounce"],"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core","usage":24},{"name":"ValidationSchema","kind":"type","signature":"export type ValidationSchema<T> = (ctx: { model: FormModel<T> }) => void;","summary":"Схема валидации — обычная функция над (под)моделью.","description":"Схема валидации — обычная функция над (под)моделью. First-class значение (можно `apply`/тестировать/переиспользовать).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","multi-step","nonexistent-api","conditional-fields","submit-and-reset","validation-strategy"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core","usage":8},{"name":"ValidationStrategyKind","kind":"type","signature":"export type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';","summary":"Когда запускается schema-валидация: - `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке); - `blur` — при потере фокуса поля (смена `touched` любого листа); - `change` — на каждый ввод (с опц.","description":"Когда запускается schema-валидация:\n- `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке);\n- `blur` — при потере фокуса поля (смена `touched` любого листа);\n- `change` — на каждый ввод (с опц. `debounce`);\n- `afterFirstSubmit` — тихо до первого `validate()`, затем live (см. `liveAfterSubmit`).","params":[],"examplesCount":0,"topics":["validation-strategy"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"ValidationStrategyOptions","kind":"interface","signature":"export interface ValidationStrategyOptions {\n /** Стратегия запуска. Default `'submit'`. */\n strategy?: ValidationStrategyKind;\n /** Debounce (мс) для live-фаз (`change` и live-часть `afterFirstSubmit`). Default `0`. */\n debounce?: number;\n /** Режим live-фазы для `afterFirstSubmit`. Default `'change'`. */\n liveAfterSubmit?: 'change' | 'blur';\n}","summary":"","params":[],"examplesCount":0,"topics":["validation-strategy"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"Validator","kind":"type","signature":"export type Validator<TForm, TField> = (\n value: TField,\n scope: unknown,\n root: FormModel<TForm>\n) => ValidationError | null;","summary":"Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).","description":"Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).","params":[],"examplesCount":0,"deprecated":"Осиротевший остаток удалённого дерево-движка (`validateFormModel`). Живой контракт —\n`Rule<T> = (value) => ValidationError | null` из `@reformer/core/validation`: правила\nпередаются в `validate(sig, [rules])` внутри `defineValidationSchema` и запускаются\n`validateModel(model, schema)`. Cross-field — оператор `cross(sig, (f) => …)` над снапшотом\n`model.get()` (третий аргумент `root` больше не нужен). Тип экспортируется только ради\nобратной совместимости (`SchemaValidator`-union).","sourcePath":"src/form/types/validation-schema.ts","entries":["."],"package":"@reformer/core"},{"name":"ValidatorFn","kind":"type","signature":"export type ValidatorFn<T = FormValue> = (value: T) => ValidationError | null;","summary":"Синхронная функция валидации","description":"Синхронная функция валидации","params":[],"examplesCount":0,"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core"},{"name":"watchField","kind":"function","signature":"export function watchField<T>(\n source: ReadonlySignal<T>,\n cb: (value: T) => void,\n options?: { immediate?: boolean }\n): BehaviorCleanup","summary":"Реакция на изменение поля: вызывает `cb(value)` при каждом изменении (по умолчанию без вызова на инициализации; `immediate: true` — вызвать сразу).","description":"Реакция на изменение поля: вызывает `cb(value)` при каждом изменении (по умолчанию без вызова на\nинициализации; `immediate: true` — вызвать сразу).","params":[],"example":"```typescript\nwatchField(model.$.country, async (country) => {\n model.city = '';\n // ... загрузить города\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","async-watchfield","reading-values","compute-vs-watch","sync-fields","type-safety-recipes"],"sourcePath":"src/model/behaviors-value.ts","entries":[".","./model"],"package":"@reformer/core","usage":4}]}