@reformer/core 11.0.0-beta.7 → 11.0.0-beta.8
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.
- package/README.md +7 -3
- package/dist/behaviors.js +211 -174
- package/dist/create-model-BBRHhHwK.js +30 -0
- package/dist/form/behaviors/collections.d.ts +14 -1
- package/dist/form/behaviors/context.d.ts +18 -2
- package/dist/form/behaviors/index.d.ts +3 -1
- package/dist/form/behaviors/internals.d.ts +0 -1
- package/dist/form/behaviors/schema-operators.d.ts +76 -0
- package/dist/form/behaviors/types.d.ts +15 -3
- package/dist/form/create-core-form.d.ts +5 -3
- package/dist/form/create-form.d.ts +28 -53
- package/dist/form/form-bundle.d.ts +136 -0
- package/dist/form/index.d.ts +6 -1
- package/dist/form/nodes/field-node.d.ts +2 -2
- package/dist/form/nodes/group-node.d.ts +1 -1
- package/dist/form/nodes/model-array-node.d.ts +10 -5
- package/dist/form/schema-controller.d.ts +137 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +22 -16
- package/dist/form/types/deep-schema.d.ts +2 -2
- package/dist/form/types/form-proxy.d.ts +1 -1
- package/dist/form/types/schema-node.d.ts +37 -12
- package/dist/form/validation/context.d.ts +6 -1
- package/dist/form/validation/index.d.ts +1 -1
- package/dist/form/validation/operators.d.ts +70 -8
- package/dist/form/validation/types.d.ts +2 -1
- package/dist/index.js +345 -254
- package/dist/model/index.d.ts +5 -0
- package/dist/model/model-nodes.d.ts +67 -4
- package/dist/model/model-signals-proxy.d.ts +8 -1
- package/dist/model/model-value-proxy.d.ts +76 -0
- package/dist/model/types.d.ts +7 -4
- package/dist/model-value-proxy-caTmAiUd.js +419 -0
- package/dist/model.js +19 -17
- package/dist/node-BeV_lV5i.js +100 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/platforms/react/index.d.ts +1 -0
- package/dist/strategy-DgzELWno.js +204 -0
- package/dist/validation.js +7 -6
- package/docs/llms/03-api-signatures.md +4 -4
- package/docs/llms/09-formschema.md +5 -5
- package/docs/llms/10-arrays.md +23 -4
- package/docs/llms/16-ui-components.md +2 -2
- package/docs/llms/18-conditional-fields.md +1 -1
- package/docs/llms/21-array-operations.md +26 -0
- package/docs/llms/28-submit-and-reset.md +3 -3
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/33-validation-strategy.md +2 -2
- package/docs/llms/35-unified-contract.md +246 -0
- package/llms-index.json +1 -1
- package/llms.txt +1148 -122
- package/package.json +1 -1
- package/dist/create-model-B8U-COv-.js +0 -103
- package/dist/model-signals-proxy-2vqS243J.js +0 -250
- package/dist/node-DGEFMTQu.js +0 -18
- package/dist/strategy-BXS_gBfz.js +0 -170
package/llms-index.json
CHANGED
|
@@ -1 +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",14],["schema",13],["email",13],["name",12],["const",11],["componentprops",10],["item",10],["component",9],["reformer",9],["label",8],["formfield",7],["properties",7],["kit",5],["input",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],["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],["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]]},{"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",28],["validate",20],["const",19],["reformer",17],["core",17],["null",16],["boolean",15],["email",15],["void",15],["required",14],["form",13],["cross",13],["field",12],["validatemodel",12],["typescript",11],["number",11],["signal",11],["definevalidationschema",10],["import",10],["from",10],["createform",9],["schema",9],["validation",9],["apply",9],["item",8],["validators",8],["над",8],["rule",8],["min",8],["правила",8],["type",8],["each",7],["validationschema",6],["sig",6],["внутри",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],["code",5],["для",5],["поля",5],["amount",5],["операторы",5],["never",5],["asyncrule",5],["per",5],["country",5],["length",4],["модели",4],["behavior",4],["seterrors",4],["валидация",4],["без",4],["раннер",4],["ошибки",4],["promise",4],["правило",4],["валидации",4],["await",4],["scope",4],["root",4],["validateasync",4],["async",4],["password",4],["return",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],["словаря",3],["loan",3],["component",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],["warning",2],["блокирует",2],["устаревший",2],["прогон",2],["отменяется",2],["валидацию",2],["фабрики",2],["возвращают",2],["принимают",2],["validator",2],["factories",2],["years",2],["messagekey",2],["i18n",2],["отдельной",2],["inputnumber",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],["form",9],["core",8],["input",8],["formmodel",8],["typescript",7],["import",7],["createform",7],["from",7],["address",7],["city",7],["myform",6],["под",5],["type",4],["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],["узла",2],["состояние",2],["createmodel",2],["validate",2],["required",2],["inputnumber",2],["selectasync",2],["checkboxwithlabel",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":[["model",44],["item",37],["items",23],["array",18],["form",17],["массив",12],["myform",11],["name",10],["value",10],["под",9],["const",9],["createform",8],["type",8],["reformer",8],["price",8],["properties",8],["schema",7],["typescript",7],["component",7],["через",7],["tags",7],["validation",7],["схеме",6],["each",6],["control",6],["push",5],["length",5],["элемента",5],["formmodel",5],["from",5],["core",5],["string",5],["значения",5],["cross",5],["модели",4],["path",4],["import",4],["kit",4],["схема",4],["узел",4],["поле",4],["validate",4],["для",4],["null",4],["per",4],["formarraysection",4],["index",4],["map",4],["div",4],["button",4],["names",4],["это",3],["modelarray",3],["removeat",3],["его",3],["createmodel",3],["input",3],["field",3],["required",3],["только",3],["или",3],["внутри",3],["definevalidationschema",3],["модель",3],["cdk",3],["formarray",3],["root",3],["массива",3],["формы",3],["плоские",3],["clear",3],["hasitems",3],["данные",2],["itemmodel",2],["subschema",2],["одного",2],["inputnumber",2],["сигнал",2],["itemschema",2],["объявляй",2],["одно",2],["значение",2],["поля",2],["форм",2],["список",2],["ничего",2],["форму",2],["целиком",2],["ручка",2],["правило",2],["запись",2],["группа",2],["ошибка",2],["пишется",2],["три",2],["layout",2],["форма",2],["схемы",2],["правила",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],["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`. Naming rule: **`form.<role>` is a form artefact, the suffix names its role** — `form.schema.*` (layout), `form.behavior.ts` (model behavior), `form.render.ts` (render-layer behavior, renderer targets only), `form.validation.ts` (validation rules); every other file has no prefix. A wizard may instead keep each step's code in `steps/<slug>/` (same file names inside) with a `steps/index.ts` aggregator. The old `renderer.schema.*` / `renderer.behavior.ts` / `renderer.wizard.tsx` / `validation.ts` names still work with a warning. No `lib/` / `schema/` / `components/` 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",46],["the",28],["model",24],["schema",21],["validation",14],["forms",14],["behavior",13],["one",12],["steps",12],["index",11],["module",10],["tsx",10],["file",9],["component",9],["layout",8],["step",8],["entry",7],["with",7],["components",7],["all",6],["renderer",6],["type",6],["folders",6],["root",5],["and",5],["wizard",5],["lib",5],["per",5],["value",5],["credit",5],["application",5],["export",5],["creditapplicationform",5],["loantype",5],["reformer",5],["folder",4],["default",4],["flat",4],["minimalist",4],["concern",4],["inline",4],["render",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],["every",3],["single",3],["whole",3],["rule",3],["names",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],["lives",2],["role",2],["its",2],["rules",2],["may",2],["keep",2],["each",2],["inside",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],["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: Input, 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":[["control",24],["label",24],["formfield",20],["value",19],["reformer",18],["componentprops",16],["email",16],["form",15],["input",15],["kit",12],["string",11],["type",10],["from",10],["import",9],["component",8],["placeholder",8],["tsx",8],["country",8],["для",7],["agree",7],["const",7],["компонент",6],["без",6],["useformcontrol",6],["errors",6],["disabled",6],["поля",5],["если",5],["schema",5],["options",5],["jsx",5],["props",5],["model",5],["testid",5],["через",5],["onchange",5],["обёртки",4],["как",4],["field",4],["обёртка",4],["react",4],["core",4],["return",4],["один",4],["span",4],["seam",4],["рендерер",4],["контрола",4],["свои",3],["driven",3],["selectasync",3],["checkboxwithlabel",3],["сам",3],["его",3],["button",3],["registrationform",3],["function",3],["компоненты",3],["prop",3],["onblur",3],["reformeradapter",3],["definefieldcontrol",3],["fields",3],["fieldnode",3],["myformfield",3],["unknown",3],["fieldadapter",3],["renderer",3],["adapter",3],["patterns",2],["default",2],["используй",2],["пиши",2],["design",2],["system",2],["особый",2],["подход",2],["схеме",2],["нет",2],["связывает",2],["integration",2],["usememo",2],["createmodel",2],["createform",2],["читает",2],["select",2],["etc",2],["всё",2],["wrong",2],["вместо",2],["схемы",2],["advanced",2],["children",2],["inputmask",2],["mask",2],["phone",2],["все",2],["статику",2],["based",2],["myformfieldprops",2],["shouldshowerror",2],["record",2],["text",2],["undefined",2],["setvalue",2],["target",2],["message",2],["dom",2],["статикой",2],["cdk",2],["компонентов",2],["тип",2],["адаптера",2],["есть",2],["fieldhandle",2],["handle",2],["ref",2],["resolvefieldadapter",2],["можно",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",15],["core",13],["value",12],["нет",11],["form",11],["import",10],["validators",9],["from",9],["validate",8],["validatemodel",8],["field",8],["email",8],["definevalidationschema",6],["correct",6],["type",6],["layout",6],["валидации",5],["cross",5],["validation",5],["wrong",5],["schema",5],["name",5],["validatewhen",4],["each",4],["раннер",4],["submit",4],["behaviors",4],["component",4],["input",4],["typescript",4],["поведение",4],["api",3],["path",3],["при",3],["ambient",3],["схема",3],["useform",3],["boolean",3],["схему",3],["существует",3],["kit",3],["поле",3],["inputfield",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],["control",2],["useformcontrol",2],["компонентов",2],["больше",2],["связывает",2],["variant",2],["mycontrol",2],["adapter",2],["отдельный",2],["mistakes",2],["сигналу",2],["правила",2],["литерал",2],["модели",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],["label",5],["useeffect",5],["setstate",5],["false",5],["onchange",5],["загрузка",4],["значений",4],["чтобы",4],["string",4],["createform",4],["core",4],["data",4],["city",4],["каждый",4],["сервера",3],["values",3],["race",3],["guard",3],["динамические",3],["опции",3],["captureinitial",3],["возвращает",3],["вызови",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],["input",2],["selectasync",2],["checkboxwithlabel",2],["type",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]]},{"id":"i18n","file":"34-i18n.md","title":"i18n — локаль, загрузка словарей и провайдер","purpose":"Подпуть `@reformer/core/i18n` — общий рантайм локализации для всех пакетов `@reformer/*`: объект локали, загрузка словарей, React-провайдер, перевод и форматирование. Язык задаётся провайдером, подгружается динамически и переключается на лету; узлы формы при этом не пересоздаются.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[{"why":"Импорт из главной бочки — i18n там нет, а второй путь импорта дал бы вторую копию контекста","bad":"import { I18nProvider } from '@reformer/core';","correctNote":"Единственный путь импорта — подпуть i18n","correct":"import { I18nProvider } from '@reformer/core/i18n';"},{"why":"Новый загрузчик на каждый рендер: кэш теряется, локаль грузится заново, первый кадр — fallback","bad":"<I18nProvider lang={lang} load={createLocaleLoader([loadCoreLocale])}>","correctNote":"Загрузчик — константа модуля: один кэш на приложение","correct":"const loadLocale = createLocaleLoader([loadCoreLocale]);\n<I18nProvider lang={lang} load={loadLocale}>"},{"why":"Число или дата типами ICU внутри сообщения — не поддерживается, сообщение не разберётся","bad":"const message = 'Итого: {total, number}';","correctNote":"Отформатировать отдельно и передать готовой строкой","correct":"t('cart.total', { total: number(total, { style: 'currency', currency: 'RUB' }) });"},{"why":"Ошибка выводится полем message: у правил без своего текста оно пустое — на экране ничего","bad":"<span>{errors[0].message}</span>;","correctNote":"Текст ошибки — через резолвер: он знает словарь локали, messageKey и явное сообщение","correct":"const message = useValidationMessage();\n<span>{message(errors[0])}</span>;"},{"why":"Настройки локали без словаря: объект без поля messages принимается за словарь сообщений","bad":"const source = () => ({ weekStartsOn: 1 });","correctNote":"Часть локали узнаётся по полю messages — отдавайте его хотя бы пустым","correct":"const source = () => ({ weekStartsOn: 1, messages: {} });"}],"examples":[],"troubleshooting":[],"seeAlso":["[28-submit-and-reset.md](./28-submit-and-reset.md) — жизненный цикл формы; смена языка его не затрагивает","[33-validation-strategy.md](./33-validation-strategy.md) — когда запускается валидация"],"sections":[{"heading":"Purpose","slug":"purpose-10"},{"heading":"Локаль — это данные","slug":"lokal-eto-dannye"},{"heading":"Формат сообщений — подмножество ICU","slug":"format-soobscheniy-podmnozhestvo-icu"},{"heading":"Загрузка локалей","slug":"zagruzka-lokaley"},{"heading":"Провайдер","slug":"provayder"},{"heading":"Хуки","slug":"huki"},{"heading":"Тексты ошибок валидации","slug":"teksty-oshibok-validatsii"},{"heading":"Что показывается, когда ключа нет","slug":"chto-pokazyvaetsya-kogda-klyucha-net"},{"heading":"Проверка словаря в тестах","slug":"proverka-slovarya-v-testah"},{"heading":"Описатели сообщений (низкий уровень)","slug":"opisateli-soobscheniy-nizkiy-uroven"},{"heading":"Anti-patterns","slug":"anti-patterns-9"},{"heading":"See also","slug":"see-also-11"}],"terms":[["reformer",22],["code",15],["core",14],["lang",14],["локаль",13],["локали",13],["locale",12],["const",11],["i18n",10],["message",10],["для",9],["при",8],["без",8],["typescript",8],["словарь",8],["date",8],["нет",8],["i18nprovider",8],["messages",7],["ключ",7],["языка",7],["import",7],["from",7],["loadlocale",7],["приложения",7],["validation",7],["cdk",7],["объект",6],["json",6],["number",6],["usei18n",6],["loadcorelocale",6],["load",6],["error",6],["span",6],["провайдер",5],["string",5],["plural",5],["источник",5],["ошибки",5],["kit",5],["tsx",5],["текст",5],["словаря",5],["react",4],["провайдера",4],["пакета",4],["сообщение",4],["weekstartson",4],["поэтому",4],["файла",4],["other",4],["createlocaleloader",4],["ядра",4],["или",4],["что",4],["ядро",4],["валидации",4],["сам",4],["cart",4],["messagekey",4],["errors",4],["total",4],["загрузка",3],["словарей",3],["пакетов",3],["язык",3],["переключается",3],["формы",3],["импорт",3],["одном",3],["icu",3],["profile",3],["ключи",3],["сообщений",3],["подстановка",3],["файл",3],["count",3],["файлов",3],["строкой",3],["целиком",3],["null",3],["ничего",3],["отказ",3],["загрузки",3],["source",3],["уже",3],["preload",3],["загрузчик",3],["путь",3],["ssr",3],["ошибок",3],["filesize",3],["только",3],["function",3],["return",3],["fallback",3],["пока",3],["английский",3],["usemessages",3],["выводится",3],["правила",3],["резолвер",3],["словаре",3],["email",3],["usevalidationmessage",3],["вне",3],["встроенный",3],["control",3],["его",3],["console",3],["описатели",3],["msg",3],["подпуть",2],["локализации",2],["работает",2],["есть",2],["английская",2],["всегда",2],["главной",2],["бочки",2],["этот",2],["модуль",2],["intl",2],["необязательно",2],["fns",2],["можно",2],["сервер",2],["сети",2],["title",2],["extendlocale",2],["patch",2],["словари",2],["сливаются",2],["важнее",2],["поля",2],["name",2],["one",2],["few",2],["many",2],["select",2],["оно",2],["внутри",2],["число",2],["языку",2],["числа",2],["отдельно",2],["передавайте",2],["готовой",2],["приложение",2],["fetchmessages",2],["встроенные",2],["чанк",2],["отдаёт",2],["запрос",2],["undefined",2],["трёх",2],["видах",2],["свой",2],["синхронный",2],["тесты",2],["тексты",2],["пакеты",2],["один",2],["плюс",2],["строки",2],["setlang",2],["value",2],["умолчанию",2],["onerror",2],["готовый",2],["вместо",2],["экране",2],["новый",2],["pending",2],["ошибка",2],["если",2],["первый",2],["рендер",2],["показывается",2],["там",2],["либо",2],["заменяет",2],["своего",2],["empty",2],["ключа",2],["таблицы",2],["текста",2],["отображении",2],["автора",2],["явное",2],["код",2],["required",2],["поле",2],["языком",2],["смена",2],["formfield",2],["ключом",2],["shouldshowerror",2],["когда",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: Input },\n price: { value: 0, component: Input },\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":4},{"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":"bindFieldProps","kind":"function","signature":"export function bindFieldProps(\n adapter: FieldAdapter | undefined,\n seam: FieldSeam,\n componentProps: Record<string, unknown> = {}\n): Record<string, unknown>","summary":"Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение в `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через `fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).","description":"Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение\nв `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через\n`fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).\nБез адаптера seam проходит как есть (`value` / `onChange` / `onBlur`).\n\n`componentProps` спредятся ПЕРВЫМИ — seam их перекрывает при совпадении ключей.","params":[["adapter","- Адаптер контрола или `undefined` (value-based контрол)."],["seam","- Значение поля и его колбэки."],["componentProps","- Прочие props контрола."]],"returns":"Плоский объект props для спреда в контрол.","examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"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":"bundledLocaleSource","kind":"function","signature":"export function bundledLocaleSource(\n loaders: Readonly<Record<string, () => Promise<FormLocale>>>\n): LocaleSource","summary":"Источник встроенных локалей пакета: по чанку на язык.","description":"Источник встроенных локалей пакета: по чанку на язык. Язык ищется точно, затем по основной части\nкода (`ru-RU` → `ru`). Языка нет — источник отдаёт `null`: подписи пакета останутся английскими,\nпока приложение не даст их своим источником.","params":[["loaders","- Код языка → ленивый импорт его локали."]],"example":"```ts\nexport const loadKitLocale = bundledLocaleSource({\n en: () => import('./en').then((m) => m.en),\n ru: () => import('./ru').then((m) => m.ru),\n});\n```","examplesCount":1,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"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","arrays","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_LOCALES","kind":"const","signature":"export const CORE_LOCALES: readonly string[]","summary":"Языки, которые ядро поставляет само.","description":"Языки, которые ядро поставляет само.","params":[],"examplesCount":0,"sourcePath":"src/locale/index.ts","entries":["./locale"],"package":"@reformer/core"},{"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":21},{"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: Input },\n{ value: model.$.password, component: Input },\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":10},{"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: Input },\n// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)\n{ value: model.$.profile.name, component: Input },\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":"createI18n","kind":"function","signature":"export function createI18n(locale: FormLocale): I18nHandle","summary":"Ручка для локали.","description":"Ручка для локали. Для одного и того же объекта локали возвращается один и тот же объект —\nручку можно держать в зависимостях хуков.","params":[["locale","- Локаль; см. {@link FormLocale}."]],"example":"```ts\nconst i18n = createI18n({ code: 'ru', messages: { 'cart.items': '{count, plural, one{# товар} few{# товара} other{# товаров}}' } });\ni18n.t('cart.items', { count: 3 }); // '3 товара'\ni18n.fileSize(1536); // '1,5 КБ'\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createLocaleLoader","kind":"function","signature":"export function createLocaleLoader(sources: readonly LocaleSource[]): LocaleLoader","summary":"Собирает загрузчик из источников.","description":"Собирает загрузчик из источников. Источники опрашиваются параллельно, а сливаются по порядку:\nпоздний перекрывает ранний. Результат кэшируется по коду языка, идущий запрос не дублируется.\nОтказ любого источника — отказ загрузки; из кэша такой запрос удаляется, и следующая попытка\nпойдёт заново. Источник, которому можно промахнуться, приложение оборачивает само:\n`(code) => source(code).catch(() => null)`.\n\nКод языка у результата — всегда запрошенный: по нему работает `Intl`, и он не должен зависеть\nот того, что написано внутри файла.","params":[["sources","- Источники в порядке возрастания приоритета."]],"example":"```ts\nimport { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';\nimport { loadKitLocale } from '@reformer/ui-kit/locale';\n\nexport const loadLocale = createLocaleLoader([\n loadKitLocale, // подписи кита — чанк нужного языка\n fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера\n]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createLocaleStore","kind":"function","signature":"export function createLocaleStore(\n load: LocaleLoad,\n lang: string,\n options: LocaleStoreOptions = {}\n): LocaleStore","summary":"Создаёт хранилище.","description":"Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —\nиз кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.\nЭто позволяет создавать хранилище прямо в рендере.","params":[["load","- Загрузчик локалей."],["lang","- Начальный язык."],["options","- См. {@link LocaleStoreOptions}."]],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createMessageFormatter","kind":"function","signature":"export function createMessageFormatter(\n locale: string,\n options: MessageFormatOptions = {}\n): (source: string, values?: MessageValues) => string","summary":"Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.","description":"Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.\nЭто внутренность `I18nService.t()` — сам `t()` добавляет сверху поиск ключа в словаре, маркер\nпропущенного ключа и откат на резервную локаль.","params":[["locale","- Локаль форматтера."],["options","- Настройки; см. {@link MessageFormatOptions}."]],"returns":"Функция `(сообщение, значения) => строка`.","example":"```ts\nconst format = createMessageFormatter('ru');\nformat('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}', { count: 5 });\n// '5 файлов'\n```","examplesCount":1,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"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":10},{"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":"DEFAULT_LOCALE","kind":"const","signature":"export const DEFAULT_LOCALE: FormLocale","summary":"Локаль «провайдера нет»: английский, пустой словарь.","description":"Локаль «провайдера нет»: английский, пустой словарь. Каждый пакет при промахе по словарю берёт\nсвою встроенную английскую таблицу, поэтому пустой словарь означает «всё по-английски».","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":"defineMessages","kind":"function","signature":"export function defineMessages<D extends Messages>(\n defaults: D\n): {\n msg(key: keyof D & string, values?: Readonly<Record<string, unknown>>): MessageDescriptor;\n}","summary":"`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при компиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе по локали форма покажет его, а не ключ.","description":"`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при\nкомпиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе\nпо локали форма покажет его, а не ключ.","params":[["defaults","- Английский словарь приложения."]],"example":"```ts\nimport appEn from './locales/en.json';\n\nexport const { msg } = defineMessages(appEn);\nmsg('profile.email.label'); // опечатка в ключе — ошибка компиляции\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"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` — элементы должны быть под-моделями (объектами); массив примитивов — одно\nзначение поля, его проверяют целиком: `validate(model.$.<массив>, [...])`.","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паттерн, что `revalidateWhen`, но без ручного перечисления зависимостей.\n\nСами массивы обход не посещает — только их элементы. Поле, привязанное к массиву целиком\n(мультивыбор), живёт на сигнале массива; чтобы увидеть и его, берите {@link eachValueSignal}.","params":[],"examplesCount":0,"sourcePath":"src/model/create-model.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"eachValueSignal","kind":"function","signature":"export function eachValueSignal<T>(\n model: FormModel<T>,\n visit: (signal: PathAwareSignal<unknown>) => void\n): void","summary":"Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.","description":"Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.\n\nОтличие от {@link eachLeafSignal} — узлы-массивы: массив бывает одним значением поля\n(`{ value: model.$.tags, component }`), и нода такого поля привязана к сигналу массива, а не\nк сигналам элементов. Обходчику, которому нужна именно НОДА поля (стратегия `blur` подписывается\nна её `touched`), одних листьев мало — он не увидел бы мультивыбор. Так строятся триггеры\n`change`/`blur` стратегий валидации (см. `createFormValidation`).","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","i18n"],"sourcePath":"src/form/validators/email.ts","entries":["./validators"],"package":"@reformer/core","usage":94},{"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","arrays","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":"extendLocale","kind":"function","signature":"export function extendLocale(base: FormLocale, patch: Partial<FormLocale>): FormLocale","summary":"Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные поля заменяются.","description":"Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные\nполя заменяются. Исходный объект не меняется.","params":[["base","- Локаль-основа, например поставляемая китом."],["patch","- Свои ключи, переопределения китовых подписей и поля локали."]],"example":"```ts\nimport { ru } from '@reformer/ui-kit/locale/ru';\n\nconst appRu = extendLocale(ru, {\n messages: { 'profile.title': 'Профиль', 'kit.formWizard.submit': 'Отправить' },\n});\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"fetchMessages","kind":"function","signature":"export function fetchMessages(url: (code: string) => string, init?: RequestInit): LocaleSource","summary":"Источник локали по сети: забирает JSON по адресу, построенному из кода языка.","description":"Источник локали по сети: забирает JSON по адресу, построенному из кода языка. Файл может быть\nсловарём (`{ \"profile.title\": \"Профиль\" }`) или локалью целиком (`{ \"code\": \"ru\", \"messages\": … }`).\nОтвет не `2xx` — ошибка загрузки.","params":[["url","- Адрес файла для языка."],["init","- Параметры запроса, передаются в `fetch` как есть."]],"example":"```ts\nfetchMessages((code) => `/locales/${code}.json`);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"FIELD_WRAPPER_ONLY_PROPS","kind":"const","signature":"export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]","summary":"Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у подписи.","description":"Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у\nподписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldAdapter","kind":"interface","signature":"export interface FieldAdapter {\n /** Проп, из которого контрол читает значение (по умолчанию `'value'`). */\n valueProp?: string;\n /** Колбэк, через который контрол эмитит изменение (по умолчанию `'onChange'`). */\n changeProp?: string;\n /**\n * Эмит контрола → значение поля (по умолчанию — как есть). `rest` — прочие props контрола\n * (например, чтобы достать `options` при резолве значения).\n */\n fromEmit?: (arg: unknown, rest: Record<string, unknown>) => unknown;\n /** Значение поля → `valueProp` контрола (коэрсия `null`/`undefined`; по умолчанию — как есть). */\n toValue?: (value: unknown) => unknown;\n /** Проброс blur нестандартным каналом (по умолчанию — `onBlur`). */\n bindBlur?: (onBlur: () => void) => Record<string, unknown>;\n /** Ключи, которые убрать из `componentProps` перед спредом в контрол. */\n strip?: string[];\n /**\n * Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:\n * errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает\n * `useFormControl(control)`.\n */\n passControl?: boolean;\n}","summary":"Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`, DOM-событие в `onChange`, …).","description":"Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,\nDOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.","params":[],"example":"Radix Checkbox\n```ts\nconst checkedAdapter: FieldAdapter = {\nvalueProp: 'checked',\nchangeProp: 'onCheckedChange',\nfromEmit: (c) => c === true,\ntoValue: (v) => v ?? false,\n};\n```","examplesCount":1,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldAdapterCarrier","kind":"interface","signature":"export interface FieldAdapterCarrier {\n reformerAdapter?: FieldAdapter;\n}","summary":"Компонент, объявивший свой диалект статикой.","description":"Компонент, объявивший свой диалект статикой.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"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 * const message = useValidationMessage(); // из '@reformer/core/i18n'\n *\n * return (\n * <ul className=\"error-list\">\n * {errors.map((error, i) => (\n * <li key={i}>{message(error)}</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 * const message = useValidationMessage(); // из '@reformer/core/i18n'\n *\n * return (\n * <div>\n * <input {...props} />\n * {shouldShowError && (\n * <span className=\"error\">{message(errors[0])}</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);\nconst message = useValidationMessage(); // из '@reformer/core/i18n'\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\">{message(state.errors[0])}</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":"FieldHandle","kind":"interface","signature":"export interface FieldHandle {\n /** Сфокусировать поле (делегирует на DOM-элемент контрола). */\n focus(): void;\n /** Снять фокус. */\n blur(): void;\n /** Проскроллить поле в область видимости. */\n scrollIntoView(opts?: ScrollIntoViewOptions): void;\n /** Живой DOM-элемент поля (или `null` до монтирования / для размонтированной ноды). */\n getElement(): HTMLElement | null;\n}","summary":"Базовый императивный контракт поля формы.","description":"Базовый императивный контракт поля формы.\n\nОбёртка поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`)\nстроит его из DOM-узла контрола. Композиты со своим handle (`open`/`close`/`reload`/…) отдают\nего как есть — он расширяет этот контракт.\n\nДостаётся из render-схемы по селектору:\n`schema.node(sel).getRef<FieldHandle>().current?.focus()`.\n\nПокрывает ТОЛЬКО императивные действия. Реактивное состояние (value / disabled / options /\nvalidation) живёт в behaviors и через handle не дублируется.","params":[],"examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.ts","entries":["."],"package":"@reformer/core","usage":4},{"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: Input,\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","arrays","ui-components","type-safety-recipes","i18n"],"sourcePath":"src/form/nodes/field-node.ts","entries":["."],"package":"@reformer/core","usage":6},{"name":"FieldSeam","kind":"interface","signature":"export interface FieldSeam {\n value: unknown;\n onChange: (value: unknown) => void;\n onBlur: () => void;\n}","summary":"Seam поля: текущее значение и value-based колбэки.","description":"Seam поля: текущее значение и value-based колбэки.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"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":"formatPattern","kind":"function","signature":"export function formatPattern(\n pattern: MessagePattern,\n values: MessageValues,\n locale: string,\n options: MessageFormatOptions = {}\n): string","summary":"Собирает строку из разобранного сообщения.","description":"Собирает строку из разобранного сообщения. Значения подставляются как текст и повторно\nне разбираются.","params":[["pattern","- Результат {@link parseMessage}."],["values","- Значения аргументов."],["locale","- Локаль для {@link Intl.PluralRules} и {@link Intl.NumberFormat} (`ru`, `en`, …)."],["options","- Настройки; см. {@link MessageFormatOptions}."]],"returns":"Готовая строка интерфейса.","examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":"FormLocale","kind":"interface","signature":"export interface FormLocale {\n /** Код языка BCP 47 (`en`, `ru`, `pt-BR`): по нему работают `Intl` и множественные формы. */\n readonly code: string;\n /** Словарь сообщений. Ключа нет — пакет подставит встроенный английский. */\n readonly messages: Messages;\n /** Первый день недели календаря. По умолчанию — воскресенье. */\n readonly weekStartsOn?: WeekStart;\n /**\n * Необязательное переопределение: объект локали `react-day-picker` / `date-fns`. Для ядра\n * непрозрачен. Без него даты форматируются через `Intl` по {@link FormLocale.code}.\n */\n readonly dateLocale?: unknown;\n /** Необязательное переопределение: формат даты в токенах `date-fns` для DatePicker. */\n readonly dateFormat?: string;\n}","summary":"Локаль формы.","description":"Локаль формы.","params":[],"example":"```ts\nconst ru: FormLocale = {\n code: 'ru',\n weekStartsOn: 1,\n messages: { 'kit.select.selected': 'Выбрано: {count}' },\n};\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"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":55},{"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: Input },\n address: {\n city: { value: '', component: Input },\n street: { value: '', component: Input },\n },\n items: [{\n title: { value: '', component: Input },\n price: { value: 0, component: Input },\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":"getFieldAdapter","kind":"function","signature":"export function getFieldAdapter(component: unknown): FieldAdapter | undefined","summary":"Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.","description":"Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.","params":[],"example":"```ts\nCheckbox.reformerAdapter = checkedAdapter;\ngetFieldAdapter(Checkbox); // → checkedAdapter\n```","examplesCount":1,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"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: Input },\n password: { valueSignal: model.$.password, component: Input },\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":"I18N_CONTEXT_MARKER","kind":"const","signature":"export const I18N_CONTEXT_MARKER","summary":"Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.","description":"Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/context.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nContextValue","kind":"interface","signature":"export interface I18nContextValue extends I18nHandle {\n /** Идёт загрузка другого языка; на экране пока прежний. Без загрузчика всегда `false`. */\n readonly pending: boolean;\n /** Ошибка последней загрузки языка. */\n readonly error: unknown;\n}","summary":"Что видит компонент: ручка активной локали и состояние её загрузки.","description":"Что видит компонент: ручка активной локали и состояние её загрузки.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/context.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nHandle","kind":"interface","signature":"export interface I18nHandle {\n /** Локаль, по которой работает ручка. */\n readonly locale: FormLocale;\n /** Код языка — то же, что `locale.code`. */\n readonly code: string;\n /** Есть ли ключ в словаре локали (встроенные английские таблицы не учитываются). */\n has(key: string): boolean;\n /**\n * Перевод ключа приложения: словарь локали → `defaultMessage` → встроенная таблица ядра → ключ.\n *\n * @param key - Ключ сообщения.\n * @param values - Значения подстановок.\n * @param defaultMessage - Английский текст на случай, если ключа в локали нет.\n */\n t(key: string, values?: MessageValues, defaultMessage?: string): string;\n /** Число по правилам языка. */\n number(value: number, options?: Intl.NumberFormatOptions): string;\n /** Дата по правилам языка. */\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /** Размер файла: `1536` → `1.5 KB` (`1,5 КБ` в русской локали). База — 1024. */\n fileSize(bytes: number): string;\n}","summary":"Ручка локали: перевод и форматирование по одному языку.","description":"Ручка локали: перевод и форматирование по одному языку.","params":[],"examplesCount":0,"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nLoadingProviderProps","kind":"interface","signature":"export interface I18nLoadingProviderProps {\n /** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */\n lang: string;\n /** Загрузчик локалей — обычно результат `createLocaleLoader`. */\n load: LocaleLoad;\n /**\n * Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр\n * мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`\n * до монтирования), первый рендер синхронный и `fallback` не показывается.\n */\n fallback?: ReactNode;\n /** Отказ загрузки. Язык на экране при этом не меняется. */\n onError?: (error: unknown, lang: string) => void;\n children?: ReactNode;\n}","summary":"Режим «провайдер грузит сам».","description":"Режим «провайдер грузит сам».","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nProvider","kind":"function","signature":"export function I18nProvider(props: I18nProviderProps): ReactElement | null","summary":"Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.","description":"Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.\nБез провайдера компоненты говорят встроенным английским.\n\nПри смене `lang` на экране остаётся прежний язык, пока новый не загрузится (`useI18n().pending`\n— для индикатора). При быстром переключении применяется только последний запрос. Вложенный\nпровайдер заменяет локаль для своего поддерева целиком.","params":[],"example":"Провайдер грузит локаль сам\n```tsx\nconst [lang, setLang] = useState('ru');\n\n<I18nProvider lang={lang} load={loadLocale}>\n<LangSwitch value={lang} onChange={setLang} />\n<CreditForm />\n</I18nProvider>\n```","examplesCount":2,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nProviderProps","kind":"type","signature":"export type I18nProviderProps = I18nStaticProviderProps | I18nLoadingProviderProps;","summary":"","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nStaticProviderProps","kind":"interface","signature":"export interface I18nStaticProviderProps {\n /** Локаль. Смена объекта переключает язык сразу. */\n locale: FormLocale;\n children?: ReactNode;\n}","summary":"Режим «готовая локаль».","description":"Режим «готовая локаль».","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"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":"isMessageDescriptor","kind":"function","signature":"export function isMessageDescriptor(value: unknown): value is MessageDescriptor","summary":"Является ли значение описателем сообщения.","description":"Является ли значение описателем сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"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":"isValueSignal","kind":"function","signature":"export function isValueSignal(value: unknown): value is Signal<unknown>","summary":"«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив дерева `model.$` (значение — массив целиком, `.value` читается и пишется).","description":"«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив\nдерева `model.$` (значение — массив целиком, `.value` читается и пишется).\n\nГруппа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой\n`createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —\nвместо голого `value instanceof Signal`, который узел-массив не проходит.","params":[],"example":"```typescript\nconst model = createModel({ name: '', tags: [] as string[], address: { city: '' } });\nisValueSignal(model.$.name); // true — лист\nisValueSignal(model.$.tags); // true — массив как значение\nisValueSignal(model.$.address); // false — группа\n```","examplesCount":1,"sourcePath":"src/model/model-signals-proxy.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"loadCoreLocale","kind":"const","signature":"export const loadCoreLocale","summary":"Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.","description":"Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.","params":[],"example":"```ts\nconst loadLocale = createLocaleLoader([loadCoreLocale, fetchMessages((code) => `/locales/${code}.json`)]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/locale/index.ts","entries":["./locale"],"package":"@reformer/core"},{"name":"LocaleIssue","kind":"interface","signature":"export interface LocaleIssue {\n readonly key: string;\n /**\n * - `syntax` — сообщение не разбирается;\n * - `missing` — ключ есть в образце, но его нет в словаре;\n * - `extra` — ключа нет в образце;\n * - `arguments` — набор подстановок не совпадает с образцом.\n */\n readonly kind: 'syntax' | 'missing' | 'extra' | 'arguments';\n readonly message: string;\n}","summary":"Что не так со словарём.","description":"Что не так со словарём.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleLoad","kind":"interface","signature":"export interface LocaleLoad {\n (code: string): Promise<FormLocale>;\n peek?(code: string): FormLocale | undefined;\n}","summary":"Функция загрузки локали.","description":"Функция загрузки локали. `peek` — необязательный синхронный кэш (есть у `createLocaleLoader`).","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleLoader","kind":"interface","signature":"export interface LocaleLoader {\n /** Загрузить локаль языка. Повторный вызов возвращает тот же промис и ту же локаль. */\n (code: string): Promise<FormLocale>;\n /** Уже загруженная локаль или `undefined`. Позволяет провайдеру отрисоваться синхронно. */\n peek(code: string): FormLocale | undefined;\n /** То же, что вызов загрузчика; имя для чтения в коде старта приложения. */\n preload(code: string): Promise<FormLocale>;\n}","summary":"Загрузчик локалей с кэшем.","description":"Загрузчик локалей с кэшем.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleSource","kind":"type","signature":"export type LocaleSource = (code: string) => LocaleSourceResult | Promise<LocaleSourceResult>;","summary":"Источник локали.","description":"Источник локали. Получает код языка, отдаёт локаль или словарь — сразу или промисом.\n`null`/`undefined` означает «для этого языка у меня ничего нет» и ошибкой не считается.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleSourceResult","kind":"type","signature":"export type LocaleSourceResult = Partial<FormLocale> | Messages | null | undefined;","summary":"Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.","description":"Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStore","kind":"interface","signature":"export interface LocaleStore {\n getState(): LocaleStoreState;\n subscribe(listener: () => void): () => void;\n /** Запросить язык. Повторный запрос того же языка, пока он грузится или уже показан, — no-op. */\n setLang(lang: string): void;\n}","summary":"Хранилище активной локали.","description":"Хранилище активной локали.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStoreOptions","kind":"interface","signature":"export interface LocaleStoreOptions {\n /** Вызывается при отказе загрузки языка, который всё ещё запрошен. */\n readonly onError?: (error: unknown, lang: string) => void;\n}","summary":"Настройки хранилища.","description":"Настройки хранилища.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStoreState","kind":"interface","signature":"export interface LocaleStoreState {\n /** Запрошенный язык. Пока `pending`, может отличаться от языка `locale`. */\n readonly lang: string;\n /** Локаль, которая сейчас на экране; `null` — ни одна ещё не загружена. */\n readonly locale: FormLocale | null;\n /** Идёт загрузка запрошенного языка. */\n readonly pending: boolean;\n /** Ошибка последней загрузки; сбрасывается следующей удачной. */\n readonly error: unknown;\n}","summary":"Состояние хранилища.","description":"Состояние хранилища. Объект неизменяемый: новая ссылка — новое состояние.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocalizableText","kind":"type","signature":"export type LocalizableText = string | MessageDescriptor;","summary":"Текст, который можно задать строкой или описателем.","description":"Текст, который можно задать строкой или описателем.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"makeElementFieldHandle","kind":"function","signature":"export function makeElementFieldHandle(el: RefObject<HTMLElement | null>): FieldHandle","summary":"Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.","description":"Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.\nВсе вызовы null-safe: до монтирования (`el.current === null`) — no-op, без исключений.","params":[],"examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.ts","entries":["."],"package":"@reformer/core"},{"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":26},{"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,"topics":["i18n"],"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","arrays","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,"topics":["i18n"],"sourcePath":"src/form/validators/max-total-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"MESSAGE_DESCRIPTOR","kind":"const","signature":"export const MESSAGE_DESCRIPTOR: unique symbol","summary":"Бренд описателя.","description":"Бренд описателя. `Symbol.for` — чтобы описатель узнавался и через границу двух копий модуля\n(разные чанки, разные версии пакета на странице).","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"messageArguments","kind":"function","signature":"export function messageArguments(\n pattern: MessagePattern,\n out: Set<string> = new Set()\n): Set<string>","summary":"Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.","description":"Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.","params":[["pattern","- Результат `parseMessage`."]],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageBranches","kind":"type","signature":"export type MessageBranches = ReadonlyMap<string, MessagePattern>;","summary":"Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.","description":"Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageDescriptor","kind":"interface","signature":"export interface MessageDescriptor {\n readonly [MESSAGE_DESCRIPTOR]: true;\n /** Ключ сообщения в словаре приложения. */\n readonly key: string;\n /** Значения подстановок. */\n readonly values?: Readonly<Record<string, unknown>>;\n /** Английский текст на случай, если ключа в локали нет. */\n readonly defaultMessage?: string;\n}","summary":"Описатель сообщения.","description":"Описатель сообщения. Намеренно без ключей `value`, `array`, `item`, `component`: обход схемы в\n`createForm` узнаёт по ним узел, а описатель узлом не является.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageFormatOptions","kind":"interface","signature":"export interface MessageFormatOptions {\n /**\n * Что подставить вместо аргумента, которого нет в значениях (или чьё значение не число там, где\n * ожидалось число). По умолчанию — заметный маркер `⟦name⟧`, тот же приём, что и для пропущенного\n * ключа: пропуск должен попадаться разработчику, а не пользователю.\n */\n readonly onMissingArgument?: (name: string) => string;\n}","summary":"Настройки форматирования.","description":"Настройки форматирования.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageNode","kind":"type","signature":"export type MessageNode =\n | { readonly kind: 'text'; readonly value: string }\n | { readonly kind: 'arg'; readonly name: string }\n | { readonly kind: 'number'; readonly name: string }\n | { readonly kind: 'plural'; readonly name: string; readonly branches: MessageBranches }\n | { readonly kind: 'select'; readonly name: string; readonly branches: MessageBranches };","summary":"Узел разобранного сообщения.","description":"Узел разобранного сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessagePattern","kind":"type","signature":"export type MessagePattern = readonly MessageNode[];","summary":"Разобранное сообщение — плоская последовательность узлов.","description":"Разобранное сообщение — плоская последовательность узлов.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"Messages","kind":"type","signature":"export type Messages = Readonly<Record<string, string>>;","summary":"Плоский словарь: ключ → сообщение в формате ICU (см.","description":"Плоский словарь: ключ → сообщение в формате ICU (см. `message-format.ts`).","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageSyntaxError","kind":"class","signature":"export class MessageSyntaxError extends Error { /* … */ }","summary":"Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.","description":"Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageValues","kind":"type","signature":"export type MessageValues = Readonly<Record<string, unknown>>;","summary":"Значения аргументов сообщения.","description":"Значения аргументов сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":55},{"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,"topics":["i18n"],"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","i18n"],"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 /**\n * Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком\n * (ручка значения массива — см. {@link ModelArraySignals}). Для пути группы — `undefined`.\n */\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: Input },\nqty: { value: item.$.qty, component: Input },\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> = Signal<V> & {\n /** Путь массива в модели (dot-нотация). */\n readonly __path: string;\n readonly length: number;\n readonly [index: number]: ModelSignalNode<U>;\n};","summary":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length` и сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава — push/removeAt/move).","description":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`\nи сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава —\npush/removeAt/move).\n\nВ отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это\nделает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),\nправило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением\nполя: мультивыбор, теги, список файлов.\n\n`V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`\nв рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих\nслучаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).","params":[],"examplesCount":0,"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":1},{"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":"msg","kind":"function","signature":"export function msg(\n key: string,\n values?: Readonly<Record<string, unknown>>,\n defaultMessage?: string\n): MessageDescriptor","summary":"Создаёт описатель сообщения.","description":"Создаёт описатель сообщения.","params":[["key","- Ключ в словаре приложения."],["values","- Значения подстановок."],["defaultMessage","- Английский текст на случай, если ключа в локали нет."]],"example":"```ts\n{ value: model.$.email, component: Input, componentProps: { label: msg('profile.email.label') } }\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"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: Input,\n * validators: [required, email]\n * });\n *\n * // GroupNode\n * const group = factory.createNode({\n * email: { value: '', component: Input },\n * password: { value: '', component: Input }\n * });\n *\n * // ArrayNode (объект)\n * const array = factory.createNode({\n * schema: { title: { value: '', component: Input } },\n * initialItems: [{ title: 'Item 1' }]\n * });\n *\n * // ArrayNode (массив) - новый формат\n * const array2 = factory.createNode([\n * { title: { value: '', component: Input } }, // 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: Input }); // → 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","i18n"],"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":"parseMessage","kind":"function","signature":"export function parseMessage(source: string): MessagePattern","summary":"Разбирает сообщение в дерево.","description":"Разбирает сообщение в дерево. Бросает {@link MessageSyntaxError} на ошибке синтаксиса и на\n`plural`/`select` без ветки `other`.","params":[["source","- Текст сообщения из словаря."]],"returns":"Разобранное сообщение для {@link formatPattern}.","example":"```ts\nparseMessage('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}');\n```","examplesCount":1,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"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","arrays","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":24},{"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","i18n"],"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":"resolveLocalized","kind":"function","signature":"export function resolveLocalized<T>(value: T, i18n: I18nHandle): T","summary":"Заменяет описатели сообщений на строки по активной локали.","description":"Заменяет описатели сообщений на строки по активной локали. Обходит массивы и простые\nобъекты-литералы — подписи опций, заголовки шагов; сигналы, React-элементы, функции и\nэкземпляры классов (узлы формы) возвращает как есть. Поддерево без описателей сохраняет ссылку:\nесли раскрывать было нечего, возвращается тот же объект.","params":[["value","- Пропсы, массив или одиночное значение."],["i18n","- Ручка активной локали (`createI18n` / `useI18n`)."]],"example":"```ts\nresolveLocalized({ label: msg('profile.email.label'), options: [{ value: 'a', label: msg('opt.a') }] }, i18n);\n// { label: 'Почта', options: [{ value: 'a', label: 'Вариант А' }] }\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/resolve-localized.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"resolveValidationError","kind":"function","signature":"export function resolveValidationError(error: ValidationError, i18n: I18nHandle): string","summary":"Отображаемый текст ошибки валидации.","description":"Отображаемый текст ошибки валидации. Порядок источников — в шапке модуля.","params":[["error","- Ошибка от правила: `code`, `message`, `messageKey`, `params`."],["i18n","- Ручка активной локали (`createI18n` / `useI18n`)."]],"example":"```ts\nresolveValidationError({ code: 'minLength', message: '', params: { minLength: 8 } }, i18n);\n// 'Enter at least 8 characters' · в русской локали — 'Не меньше 8 символов'\n\nresolveValidationError({ code: 'required', message: 'Укажите телефон' }, i18n);\n// 'Укажите телефон' — явное сообщение автора важнее словаря\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":"translateBuiltin","kind":"function","signature":"export function translateBuiltin(\n locale: FormLocale,\n builtin: Messages,\n key: string,\n values?: MessageValues\n): string","summary":"Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам ключ.","description":"Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам\nключ. Английская таблица форматируется по английским правилам — её множественные формы и\nгруппировка чисел не зависят от активного языка.","params":[["locale","- Активная локаль."],["builtin","- Встроенная английская таблица пакета-владельца ключа."],["key","- Ключ сообщения."],["values","- Значения подстановок."]],"examplesCount":0,"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":"useFieldHandleRef","kind":"function","signature":"export function useFieldHandleRef<H = FieldHandle>(\n outerRef: Ref<H> | undefined\n): RefObject<unknown>","summary":"Ref для контрола поля + императивный handle для потребителя.","description":"Ref для контрола поля + императивный handle для потребителя.\n\nВозвращённый ref вешается на контрол. На `outerRef` публикуется:\n- handle самого контрола, если тот его реализует (`useImperativeHandle` композита);\n- иначе — базовый {@link FieldHandle}, построенный из DOM-узла контрола.\n\nХук вызывается безусловно; вешать ref на контрол стоит только при заданном `outerRef` —\nтак function-компонент без ref-поддержки не получает лишний ref.","params":[["outerRef","- ref потребителя (из `forwardRef` или `schema.node(sel).getRef()`)."]],"returns":"ref, который нужно передать контролу.","examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.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":20},{"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 { useValidationMessage } from '@reformer/core/i18n';\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// Текст ошибки даёт резолвер: у правил без своего сообщения `message` пуст.\nconst message = useValidationMessage();\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 {message(errors[0])}\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","i18n"],"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":"useI18n","kind":"function","signature":"export function useI18n(): I18nContextValue","summary":"Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние загрузки языка.","description":"Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние\nзагрузки языка. Без провайдера — английский с пустым словарём.","params":[],"example":"```tsx\nfunction LangBadge() {\n const { code, pending, t } = useI18n();\n return <span aria-busy={pending}>{t('app.language')}: {code}</span>;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"useMessages","kind":"function","signature":"export function useMessages<D extends Messages>(\n defaults: D\n): (key: keyof D & string, values?: MessageValues) => string","summary":"Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую таблицу этого пакета.","description":"Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую\nтаблицу этого пакета. Так пакет говорит по-английски без провайдера и не зависит от того, знает\nли локаль его ключи. Тип ключа выводится из таблицы — опечатка в ключе не скомпилируется.\n\n`defaults` должен быть стабильной ссылкой (импорт JSON-модуля), иначе переводчик будет\nпересоздаваться на каждый рендер.","params":[["defaults","- Встроенная английская таблица пакета."]],"returns":"Функция `(ключ, значения?) => строка`; её идентичность меняется вместе с локалью.","example":"```tsx\nimport en from './locale/en.json';\n\nconst useKitMessages = () => useMessages(en);\n\nfunction ClearButton() {\n const t = useKitMessages();\n return <button aria-label={t('kit.select.clear')} />;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"useValidationMessage","kind":"function","signature":"export function useValidationMessage(): (error: ValidationError) => string","summary":"Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` → словарь локали по коду → встроенный английский → код.","description":"Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` →\nсловарь локали по коду → встроенный английский → код. Идентичность функции меняется вместе с\nлокалью, поэтому показанные ошибки переводятся при смене языка без повторной валидации.\n\n`FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете ошибки\nсвоим компонентом.","params":[],"example":"```tsx\nfunction FieldErrors({ control }: { control: FieldNode<string> }) {\n const message = useValidationMessage();\n const { errors } = useFormControl(control);\n return <>{errors.map((error) => <span key={error.code}>{message(error)}</span>)}</>;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"validate","kind":"function","signature":"export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void","summary":"Синхронные правила поля.","description":"Синхронные правила поля.\n\nПоле — лист либо массив целиком: `model.$.<массив>` — такая же ручка значения, и правило\nполучает массив (мультивыбор, теги, список файлов).","params":[],"example":"```ts\nvalidate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);\nvalidate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение\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","i18n"],"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":"validateLocale","kind":"function","signature":"export function validateLocale(messages: Messages, reference?: Messages): LocaleIssue[]","summary":"Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена подстановок совпадают с ним.","description":"Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена\nподстановок совпадают с ним. Нужна там, где компилятор не поможет: словарь пришёл по сети или\nнаписан вне репозитория. В рантайме вызывать не обязательно — битое или пропущенное сообщение\nи так откатится на встроенный английский; проверка нужна тестам и CI.","params":[["messages","- Проверяемый словарь."],["reference","- Образец, обычно английский словарь того же пакета."]],"returns":"Список расхождений; пустой — словарь в порядке.","example":"```ts\nexpect(validateLocale(ruJson, enJson)).toEqual([]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"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` (`validation.<code>`; см. `resolveValidationError` в\n * `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —\n * для локализуемого текста используйте {@link ValidateOptions.messageKey}. */\n message?: string;\n /**\n * Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в\n * активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.\n *\n * @example\n * ```ts\n * required({ messageKey: 'profile.name.required', message: 'Name is required' });\n * ```\n */\n messageKey?: 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 /**\n * Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку\n * тогда даёт резолвер по `code` (словарь локали `validation.<code>`).\n */\n message: string;\n /** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */\n messageKey?: 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},{"name":"WeekStart","kind":"type","signature":"export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;","summary":"День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.","description":"День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"}]}
|
|
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",14],["schema",13],["email",13],["name",12],["const",11],["componentprops",10],["item",10],["component",9],["reformer",9],["label",8],["formfield",7],["properties",7],["kit",5],["input",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],["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],["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]]},{"id":"api-signatures","file":"03-api-signatures.md","title":"api-signatures","purpose":"// Форма (ноды поверх сигналов модели) createFormFromModel<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",28],["validate",20],["const",19],["reformer",17],["core",17],["null",16],["boolean",15],["email",15],["void",15],["required",14],["form",13],["cross",13],["field",12],["validatemodel",12],["typescript",11],["number",11],["signal",11],["definevalidationschema",10],["import",10],["from",10],["schema",9],["validation",9],["apply",9],["item",8],["validators",8],["над",8],["rule",8],["min",8],["правила",8],["type",8],["each",7],["createform",6],["validationschema",6],["sig",6],["внутри",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],["code",5],["для",5],["поля",5],["amount",5],["операторы",5],["never",5],["asyncrule",5],["per",5],["country",5],["length",4],["модели",4],["createformfrommodel",4],["behavior",4],["seterrors",4],["валидация",4],["без",4],["раннер",4],["ошибки",4],["promise",4],["правило",4],["валидации",4],["await",4],["scope",4],["root",4],["validateasync",4],["async",4],["password",4],["return",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],["словаря",3],["loan",3],["component",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],["warning",2],["блокирует",2],["устаревший",2],["прогон",2],["отменяется",2],["валидацию",2],["фабрики",2],["возвращают",2],["принимают",2],["validator",2],["factories",2],["years",2],["messagekey",2],["i18n",2],["отдельной",2],["inputnumber",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],["form",9],["core",8],["input",8],["formmodel",8],["typescript",7],["import",7],["from",7],["address",7],["city",7],["myform",6],["под",5],["type",4],["createformfrommodel",4],["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],["узла",2],["состояние",2],["createmodel",2],["validate",2],["required",2],["inputnumber",2],["selectasync",2],["checkboxwithlabel",2],["myvalidation",2],["inactive",2],["sub",2],["api",2],["builder",2],["элемента",2],["массива",2],["addressnodes",2],["itemschema",2],["createform",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":[["model",46],["item",41],["array",23],["items",23],["массив",20],["form",19],["name",12],["myform",11],["const",11],["value",11],["под",10],["schema",8],["typescript",8],["type",8],["reformer",8],["price",8],["component",8],["properties",8],["через",7],["tags",7],["validation",7],["схеме",6],["formmodel",6],["массива",6],["each",6],["control",6],["push",5],["length",5],["элемента",5],["from",5],["core",5],["string",5],["схема",5],["узел",5],["значения",5],["cross",5],["модели",4],["path",4],["import",4],["createformfrommodel",4],["input",4],["kit",4],["поле",4],["validate",4],["для",4],["null",4],["contact",4],["внутри",4],["createform",4],["per",4],["formarraysection",4],["index",4],["map",4],["div",4],["button",4],["names",4],["это",3],["modelarray",3],["removeat",3],["его",3],["createmodel",3],["field",3],["required",3],["только",3],["группа",3],["или",3],["группе",3],["строке",3],["схемы",3],["definevalidationschema",3],["модель",3],["cdk",3],["formarray",3],["root",3],["формы",3],["плоские",3],["clear",3],["hasitems",3],["массивы",2],["данные",2],["itemmodel",2],["subschema",2],["одного",2],["inputnumber",2],["сигнал",2],["itemschema",2],["объявляй",2],["одно",2],["значение",2],["поля",2],["форм",2],["список",2],["ничего",2],["форму",2],["целиком",2],["ручка",2],["правило",2],["получает",2],["запись",2],["так",2],["ошибка",2],["пишется",2],["ноду",2],["строки",2],["contactitem",2],["phones",2],["phoneitem",2],["hotlines",2],["contacts",2],["operations",2],["три",2],["layout",2],["форма",2],["правила",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],["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`. Naming rule: **`form.<role>` is a form artefact, the suffix names its role** — `form.schema.*` (layout), `form.behavior.ts` (model behavior), `form.render.ts` (render-layer behavior, renderer targets only), `form.validation.ts` (validation rules); every other file has no prefix. A wizard may instead keep each step's code in `steps/<slug>/` (same file names inside) with a `steps/index.ts` aggregator. The old `renderer.schema.*` / `renderer.behavior.ts` / `renderer.wizard.tsx` / `validation.ts` names still work with a warning. No `lib/` / `schema/` / `components/` 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",46],["the",28],["model",24],["schema",21],["validation",14],["forms",14],["behavior",13],["one",12],["steps",12],["index",11],["module",10],["tsx",10],["file",9],["component",9],["layout",8],["step",8],["entry",7],["with",7],["components",7],["all",6],["renderer",6],["type",6],["folders",6],["root",5],["and",5],["wizard",5],["lib",5],["per",5],["value",5],["credit",5],["application",5],["export",5],["creditapplicationform",5],["loantype",5],["reformer",5],["folder",4],["default",4],["flat",4],["minimalist",4],["concern",4],["inline",4],["render",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],["every",3],["single",3],["whole",3],["rule",3],["names",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],["lives",2],["role",2],["its",2],["rules",2],["may",2],["keep",2],["each",2],["inside",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],["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: Input, 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":[["control",24],["label",24],["formfield",20],["value",19],["reformer",18],["componentprops",16],["email",16],["form",15],["input",15],["kit",12],["string",11],["type",10],["from",10],["import",9],["component",8],["placeholder",8],["tsx",8],["country",8],["для",7],["agree",7],["const",7],["компонент",6],["без",6],["useformcontrol",6],["errors",6],["disabled",6],["поля",5],["если",5],["schema",5],["options",5],["jsx",5],["props",5],["model",5],["testid",5],["через",5],["onchange",5],["обёртки",4],["как",4],["field",4],["обёртка",4],["react",4],["core",4],["return",4],["один",4],["span",4],["seam",4],["рендерер",4],["контрола",4],["свои",3],["driven",3],["selectasync",3],["checkboxwithlabel",3],["сам",3],["его",3],["button",3],["registrationform",3],["function",3],["компоненты",3],["prop",3],["onblur",3],["reformeradapter",3],["definefieldcontrol",3],["fields",3],["fieldnode",3],["myformfield",3],["unknown",3],["fieldadapter",3],["renderer",3],["adapter",3],["patterns",2],["default",2],["используй",2],["пиши",2],["design",2],["system",2],["особый",2],["подход",2],["схеме",2],["нет",2],["связывает",2],["integration",2],["usememo",2],["createmodel",2],["createformfrommodel",2],["читает",2],["select",2],["etc",2],["всё",2],["wrong",2],["вместо",2],["схемы",2],["advanced",2],["children",2],["inputmask",2],["mask",2],["phone",2],["все",2],["статику",2],["based",2],["myformfieldprops",2],["shouldshowerror",2],["record",2],["text",2],["undefined",2],["setvalue",2],["target",2],["message",2],["dom",2],["статикой",2],["cdk",2],["компонентов",2],["тип",2],["адаптера",2],["есть",2],["fieldhandle",2],["handle",2],["ref",2],["resolvefieldadapter",2],["можно",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",15],["core",13],["value",12],["нет",11],["form",11],["import",10],["validators",9],["from",9],["validate",8],["validatemodel",8],["field",8],["email",8],["definevalidationschema",6],["correct",6],["type",6],["layout",6],["валидации",5],["cross",5],["validation",5],["wrong",5],["schema",5],["name",5],["validatewhen",4],["each",4],["раннер",4],["submit",4],["behaviors",4],["component",4],["input",4],["typescript",4],["поведение",4],["api",3],["path",3],["при",3],["ambient",3],["схема",3],["useform",3],["boolean",3],["схему",3],["существует",3],["kit",3],["поле",3],["inputfield",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],["control",2],["useformcontrol",2],["компонентов",2],["больше",2],["связывает",2],["variant",2],["mycontrol",2],["adapter",2],["отдельный",2],["mistakes",2],["сигналу",2],["правила",2],["литерал",2],["модели",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",30],["items",21],["item",18],["form",9],["const",9],["array",8],["reformer",7],["applyeach",7],["typescript",6],["name",6],["price",6],["core",6],["defineformbehavior",6],["contact",6],["под",5],["index",5],["import",5],["from",5],["myform",5],["row",5],["через",4],["формы",4],["map",4],["модель",4],["length",4],["push",4],["return",4],["div",4],["button",4],["phone",4],["address",4],["rows",4],["cross",4],["рендер",3],["formproxy",3],["removeat",3],["value",3],["control",3],["remove",3],["add",3],["per",3],["behavior",3],["enablewhen",3],["внутри",3],["строки",3],["aggregateinto",3],["validation",3],["sum",3],["объектов",2],["modelarray",2],["ноду",2],["formmodel",2],["undefined",2],["плоские",2],["значения",2],["индексу",2],["fieldconfig",2],["component",2],["object",2],["usearraylength",2],["formfield",2],["onclick",2],["compute",2],["behaviors",2],["массив",2],["массива",2],["apply",2],["ручка",2],["нода",2],["phonebehavior",2],["contactbehavior",2],["phones",2],["фасад",2],["modelof",2],["get",2],["patch",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":"createFormFromModel({ 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],["возвращает",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],["createform",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],["createformfrommodel",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 = createFormFromModel({ model, schema });\n}","correctNote":"Стабильные ссылки через useMemo","correct":"function MyForm() {\n const { model, form } = useMemo(() => {\n const m = createModel<T>(initial);\n return { model: m, form: createFormFromModel({ 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],["label",5],["useeffect",5],["setstate",5],["false",5],["onchange",5],["загрузка",4],["значений",4],["чтобы",4],["string",4],["createformfrommodel",4],["core",4],["data",4],["city",4],["каждый",4],["сервера",3],["values",3],["race",3],["guard",3],["динамические",3],["опции",3],["captureinitial",3],["возвращает",3],["вызови",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],["input",2],["selectasync",2],["checkboxwithlabel",2],["type",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],["createformfrommodel",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]]},{"id":"i18n","file":"34-i18n.md","title":"i18n — локаль, загрузка словарей и провайдер","purpose":"Подпуть `@reformer/core/i18n` — общий рантайм локализации для всех пакетов `@reformer/*`: объект локали, загрузка словарей, React-провайдер, перевод и форматирование. Язык задаётся провайдером, подгружается динамически и переключается на лету; узлы формы при этом не пересоздаются.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[{"why":"Импорт из главной бочки — i18n там нет, а второй путь импорта дал бы вторую копию контекста","bad":"import { I18nProvider } from '@reformer/core';","correctNote":"Единственный путь импорта — подпуть i18n","correct":"import { I18nProvider } from '@reformer/core/i18n';"},{"why":"Новый загрузчик на каждый рендер: кэш теряется, локаль грузится заново, первый кадр — fallback","bad":"<I18nProvider lang={lang} load={createLocaleLoader([loadCoreLocale])}>","correctNote":"Загрузчик — константа модуля: один кэш на приложение","correct":"const loadLocale = createLocaleLoader([loadCoreLocale]);\n<I18nProvider lang={lang} load={loadLocale}>"},{"why":"Число или дата типами ICU внутри сообщения — не поддерживается, сообщение не разберётся","bad":"const message = 'Итого: {total, number}';","correctNote":"Отформатировать отдельно и передать готовой строкой","correct":"t('cart.total', { total: number(total, { style: 'currency', currency: 'RUB' }) });"},{"why":"Ошибка выводится полем message: у правил без своего текста оно пустое — на экране ничего","bad":"<span>{errors[0].message}</span>;","correctNote":"Текст ошибки — через резолвер: он знает словарь локали, messageKey и явное сообщение","correct":"const message = useValidationMessage();\n<span>{message(errors[0])}</span>;"},{"why":"Настройки локали без словаря: объект без поля messages принимается за словарь сообщений","bad":"const source = () => ({ weekStartsOn: 1 });","correctNote":"Часть локали узнаётся по полю messages — отдавайте его хотя бы пустым","correct":"const source = () => ({ weekStartsOn: 1, messages: {} });"}],"examples":[],"troubleshooting":[],"seeAlso":["[28-submit-and-reset.md](./28-submit-and-reset.md) — жизненный цикл формы; смена языка его не затрагивает","[33-validation-strategy.md](./33-validation-strategy.md) — когда запускается валидация"],"sections":[{"heading":"Purpose","slug":"purpose-10"},{"heading":"Локаль — это данные","slug":"lokal-eto-dannye"},{"heading":"Формат сообщений — подмножество ICU","slug":"format-soobscheniy-podmnozhestvo-icu"},{"heading":"Загрузка локалей","slug":"zagruzka-lokaley"},{"heading":"Провайдер","slug":"provayder"},{"heading":"Хуки","slug":"huki"},{"heading":"Тексты ошибок валидации","slug":"teksty-oshibok-validatsii"},{"heading":"Что показывается, когда ключа нет","slug":"chto-pokazyvaetsya-kogda-klyucha-net"},{"heading":"Проверка словаря в тестах","slug":"proverka-slovarya-v-testah"},{"heading":"Описатели сообщений (низкий уровень)","slug":"opisateli-soobscheniy-nizkiy-uroven"},{"heading":"Anti-patterns","slug":"anti-patterns-9"},{"heading":"See also","slug":"see-also-11"}],"terms":[["reformer",22],["code",15],["core",14],["lang",14],["локаль",13],["локали",13],["locale",12],["const",11],["i18n",10],["message",10],["для",9],["при",8],["без",8],["typescript",8],["словарь",8],["date",8],["нет",8],["i18nprovider",8],["messages",7],["ключ",7],["языка",7],["import",7],["from",7],["loadlocale",7],["приложения",7],["validation",7],["cdk",7],["объект",6],["json",6],["number",6],["usei18n",6],["loadcorelocale",6],["load",6],["error",6],["span",6],["провайдер",5],["string",5],["plural",5],["источник",5],["ошибки",5],["kit",5],["tsx",5],["текст",5],["словаря",5],["react",4],["провайдера",4],["пакета",4],["сообщение",4],["weekstartson",4],["поэтому",4],["файла",4],["other",4],["createlocaleloader",4],["ядра",4],["или",4],["что",4],["ядро",4],["валидации",4],["сам",4],["cart",4],["messagekey",4],["errors",4],["total",4],["загрузка",3],["словарей",3],["пакетов",3],["язык",3],["переключается",3],["формы",3],["импорт",3],["одном",3],["icu",3],["profile",3],["ключи",3],["сообщений",3],["подстановка",3],["файл",3],["count",3],["файлов",3],["строкой",3],["целиком",3],["null",3],["ничего",3],["отказ",3],["загрузки",3],["source",3],["уже",3],["preload",3],["загрузчик",3],["путь",3],["ssr",3],["ошибок",3],["filesize",3],["только",3],["function",3],["return",3],["fallback",3],["пока",3],["английский",3],["usemessages",3],["выводится",3],["правила",3],["резолвер",3],["словаре",3],["email",3],["usevalidationmessage",3],["вне",3],["встроенный",3],["control",3],["его",3],["console",3],["описатели",3],["msg",3],["подпуть",2],["локализации",2],["работает",2],["есть",2],["английская",2],["всегда",2],["главной",2],["бочки",2],["этот",2],["модуль",2],["intl",2],["необязательно",2],["fns",2],["можно",2],["сервер",2],["сети",2],["title",2],["extendlocale",2],["patch",2],["словари",2],["сливаются",2],["важнее",2],["поля",2],["name",2],["one",2],["few",2],["many",2],["select",2],["оно",2],["внутри",2],["число",2],["языку",2],["числа",2],["отдельно",2],["передавайте",2],["готовой",2],["приложение",2],["fetchmessages",2],["встроенные",2],["чанк",2],["отдаёт",2],["запрос",2],["undefined",2],["трёх",2],["видах",2],["свой",2],["синхронный",2],["тесты",2],["тексты",2],["пакеты",2],["один",2],["плюс",2],["строки",2],["setlang",2],["value",2],["умолчанию",2],["onerror",2],["готовый",2],["вместо",2],["экране",2],["новый",2],["pending",2],["ошибка",2],["если",2],["первый",2],["рендер",2],["показывается",2],["там",2],["либо",2],["заменяет",2],["своего",2],["empty",2],["ключа",2],["таблицы",2],["текста",2],["отображении",2],["автора",2],["явное",2],["код",2],["required",2],["поле",2],["языком",2],["смена",2],["formfield",2],["ключом",2],["shouldshowerror",2],["когда",2]]},{"id":"unified-contract","file":"35-unified-contract.md","title":"Единый контракт формы — одна схема, одна сборка, одно поведение","purpose":"Форма описывается одинаково для всех способов реализации — разметка в JSX, рендерер по TS-схеме, рендерер по JSON. Схема одна (дерево узлов), привязка одна (ручка `model.$.…`), сборка одна (`createForm`), поведение одно (`defineFormBehavior`). Способы отличаются только видом схемы (билдер или документ) и тем, кто рисует результат.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"Purpose","slug":"purpose-11"},{"heading":"Модель — начальные значения и шаблоны элементов","slug":"model-nachalnye-znacheniya-i-shablony-elementov"},{"heading":"Схема — одно дерево","slug":"shema-odno-derevo"},{"heading":"Валидация — подформа и массив подключаются привязкой","slug":"validatsiya-podforma-i-massiv-podklyuchayutsya-privyazkoy"},{"heading":"Поведение — модель, форма и схема","slug":"povedenie-model-forma-i-shema"},{"heading":"Сборка — `createForm`","slug":"sborka-createform"},{"heading":"Частые ошибки","slug":"chastye-oshibki"}],"terms":[["model",78],["schema",18],["node",15],["массив",13],["apply",13],["const",13],["component",12],["address",12],["схема",11],["без",11],["узел",11],["под",10],["applyeach",10],["модель",10],["модели",9],["part",8],["arrayof",8],["шаблон",8],["массива",8],["поведение",7],["дерево",7],["createform",7],["items",7],["item",7],["city",7],["одна",6],["одно",6],["как",6],["значения",6],["сборка",5],["рендерер",5],["json",5],["или",5],["поле",5],["подформа",5],["правило",5],["loantype",5],["push",5],["import",5],["from",5],["reformer",5],["core",5],["coborrower",5],["creditform",5],["registrationaddress",5],["street",5],["children",5],["componentprops",5],["form",5],["узла",5],["области",5],["hidewhen",5],["форма",4],["для",4],["jsx",4],["defineformbehavior",4],["схемы",4],["форм",4],["group",4],["validate",4],["rules",4],["typescript",4],["createmodel",4],["export",4],["coborrowers",4],["элемента",4],["поля",4],["внутри",4],["шаблона",4],["formmodel",4],["label",4],["selector",4],["cross",4],["definevalidationschema",4],["validation",4],["credit",4],["формы",3],["узлов",3],["привязка",3],["ручка",3],["только",3],["билдер",3],["документ",3],["value",3],["оператор",3],["пока",3],["валидация",3],["mortgage",3],["область",3],["name",3],["нового",3],["его",3],["один",3],["раз",3],["получает",3],["box",3],["input",3],["функция",3],["нет",3],["данных",3],["подформы",3],["addressrules",3],["через",3],["корень",3],["oncomponentevent",3],["onmount",3],["части",3],["ismortgage",3],["строки",3],["registry",3],["разметка",2],["записи",2],["ключи",2],["array",2],["each",2],["так",2],["itembehavior",2],["начальные",2],["blankphone",2],["number",2],["blankcoborrower",2],["phones",2],["createcreditmodel",2],["tags",2],["значение",2],["нужен",2],["blank",2],["обычный",2],["вложенный",2],["загрузка",2],["получают",2],["бросает",2],["ошибку",2],["structuredclone",2],["фабрике",2],["creditschema",2],["группа",2],["есть",2],["той",2],["вызывается",2],["validatewhen",2],["required",2],["coborrowerrules",2],["contactsrules",2],["residenceaddress",2],["снапшот",2],["принимает",2],["enablewhen",2],["addressbehavior",2],["ищется",2],["creditbehavior",2],["операторы",2],["монтирования",2],["sel",2],["видимость",2],["getref",2],["ref",2],["дереве",2],["корня",2],["ничего",2],["useformbundle",2],["initial",2],["render",2],["formfield",2],["всегда",2],["фабрика",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<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void","summary":"Применить под-схему к одному или нескольким полям-группам (переиспользование).","description":"Применить под-схему к одному или нескольким полям-группам (переиспользование).\n\nПод-схема получает scope группы: `model` — настоящая под-модель (`model.$.field`, чтение и\nзапись значений, `get`/`set`/`patch`), `form` — нода группы. Цель — ручка группы\n(`model.$.address`); работает и в корневой схеме, и в схеме строки массива.","params":[],"example":"apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","multi-step","extended-mistakes","nonexistent-api","array-operations","sync-fields","submit-and-reset","unified-contract"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors","./validation"],"package":"@reformer/core","usage":6},{"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}).\n\nРаботает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки\nможно снова вызвать `applyEach`.","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 applyEach(row.$.phones, phoneBehavior); // массив внутри строки\n}));","examplesCount":1,"topics":["api-reference","api-signatures","troubleshooting","complete-import","arrays","extended-mistakes","array-operations","unified-contract"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors","./validation"],"package":"@reformer/core","usage":5},{"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: Input },\n price: { value: 0, component: Input },\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":4},{"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":"arrayOf","kind":"function","signature":"export function arrayOf<U>(blank: () => U, items: readonly U[] = []): U[]","summary":"Массив модели с шаблоном нового элемента.","description":"Массив модели с шаблоном нового элемента.\n\nШаблон — фабрика значения, которое получает `push()` / `insertAt(i)` без аргумента: кнопка\n«Добавить» секции массива, `model.items.push()` в поведении. Объявляется там же, где остальные\nначальные значения, — в модели; схеме и JSX знать его не нужно.\n\nВозвращает обычный массив: тип поля остаётся `U[]`. Шаблон переживает `set` / `patch` / `reset`\nмодели. Вложенные массивы объявляются внутри шаблона элемента — тогда и элементы, пришедшие\nобычными данными (загрузка с сервера), получают их шаблоны.\n\nШаблон привязан к самому массиву, поэтому до `createModel` его нельзя копировать:\n`structuredClone`, `JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона, и\n`push()` без значения бросит ошибку. Начальные значения с `arrayOf` держат в фабрике\n(`const initial = () => ({ items: arrayOf(blank) })`), а не в константе, которую клонируют.","params":[["blank","- Фабрика нового элемента. Вызывается на каждое добавление — значение не делится\nмежду элементами."],["items","- Начальные элементы. По умолчанию массив пуст."]],"returns":"Массив начальных значений с шаблоном.","example":"```typescript\nconst blankPhone = () => ({ number: '' });\nconst blankCoBorrower = () => ({ name: '', phones: arrayOf(blankPhone) });\n\nconst model = createModel({ coBorrowers: arrayOf(blankCoBorrower) });\n\nmodel.coBorrowers.push(); // { name: '', phones: [] }\nmodel.coBorrowers.at(0).phones.push(); // { number: '' }\nmodel.coBorrowers.push({ name: 'Анна', phones: [] }); // значение целиком — как раньше\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/model/model-nodes.ts","entries":[".","./model"],"package":"@reformer/core","usage":6},{"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> = {\n model: FormModel<T>;\n form: FormProxy<T>;\n schema: SchemaScope;\n};","summary":"Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).","description":"Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).\n\n`schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении\nподформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов\n(`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.","params":[],"examplesCount":0,"sourcePath":"src/form/behaviors/types.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"bindFieldProps","kind":"function","signature":"export function bindFieldProps(\n adapter: FieldAdapter | undefined,\n seam: FieldSeam,\n componentProps: Record<string, unknown> = {}\n): Record<string, unknown>","summary":"Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение в `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через `fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).","description":"Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение\nв `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через\n`fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).\nБез адаптера seam проходит как есть (`value` / `onChange` / `onBlur`).\n\n`componentProps` спредятся ПЕРВЫМИ — seam их перекрывает при совпадении ключей.","params":[["adapter","- Адаптер контрола или `undefined` (value-based контрол)."],["seam","- Значение поля и его колбэки."],["componentProps","- Прочие props контрола."]],"returns":"Плоский объект props для спреда в контрол.","examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"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":"bundledLocaleSource","kind":"function","signature":"export function bundledLocaleSource(\n loaders: Readonly<Record<string, () => Promise<FormLocale>>>\n): LocaleSource","summary":"Источник встроенных локалей пакета: по чанку на язык.","description":"Источник встроенных локалей пакета: по чанку на язык. Язык ищется точно, затем по основной части\nкода (`ru-RU` → `ru`). Языка нет — источник отдаёт `null`: подписи пакета останутся английскими,\nпока приложение не даст их своим источником.","params":[["loaders","- Код языка → ленивый импорт его локали."]],"example":"```ts\nexport const loadKitLocale = bundledLocaleSource({\n en: () => import('./en').then((m) => m.en),\n ru: () => import('./ru').then((m) => m.ru),\n});\n```","examplesCount":1,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"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","unified-contract"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":23},{"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":5},{"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","arrays","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":9},{"name":"CORE_LOCALES","kind":"const","signature":"export const CORE_LOCALES: readonly string[]","summary":"Языки, которые ядро поставляет само.","description":"Языки, которые ядро поставляет само.","params":[],"examplesCount":0,"sourcePath":"src/locale/index.ts","entries":["./locale"],"package":"@reformer/core"},{"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"},{"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,"deprecated":"Пишите `createForm` — та же сборка, в бандле вдобавок дерево для рендерера.","topics":["quick-start","project-structure","unified-contract"],"sourcePath":"src/form/create-core-form.ts","entries":["."],"package":"@reformer/core"},{"name":"CreateCoreFormConfig","kind":"interface","signature":"export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {\n /**\n * Билдер схемы формы. Именно функция, а не готовое дерево: узлы схемы держат ручки модели\n * (`model: 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 extends object>(\n config: CreateFormConfig<T> & { validation: FormValidation<T> | ValidationSchema<T> }\n): ValidatedFormBundle<T>;","summary":"Собрать модель, форму, валидацию и дерево для рендера за один проход.","description":"Собрать модель, форму, валидацию и дерево для рендера за один проход.","params":[["config","- {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,\n`behavior`, `validation`, `seed`, `setup`."]],"returns":"","example":"Рендерер по TS-схеме\n```tsx\nconst credit = useFormBundle(() =>\ncreateForm<CreditForm>({\nmodel: createCreditModel(),\nschema: creditSchema, // (model) => узел\nbehavior: creditBehavior,\nvalidation: { steps: { loan: loanRules }, extras: crossRules },\n})\n);\nreturn <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;\n```","examplesCount":3,"topics":["api-reference","api-signatures","common-mistakes","troubleshooting","complete-import","formschema","arrays","nonexistent-api","compute-vs-watch","copy-from","reset-when","transform-value","submit-and-reset","type-safety-recipes","async-validator-debounce","unified-contract"],"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":48},{"name":"CreateFormConfig","kind":"interface","signature":"export interface CreateFormConfig<T> {\n /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */\n initial?: T;\n /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */\n model?: FormModel<T>;\n /**\n * Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.\n * Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить\n * дерево до модели нечем.\n */\n schema?: ((model: FormModel<T>) => FormSchemaNode) | object;\n /** Реестр JSON-варианта: превращает документ схемы в дерево. */\n registry?: SchemaResolver;\n /** Поведение формы: значения, состояние нод и правила узлов схемы. */\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 setup?: (bundle: FormBundle<T>) => void;\n}","summary":"Конфиг {@link createForm}.","description":"Конфиг {@link createForm}.","params":[],"examplesCount":0,"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core"},{"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 * сборкой формы: поле, чьё значение уже массив, ноды не получает (см. `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) из схемы, материализует массивы\nпод-форм как {@link ModelArrayNode} (на любой глубине), заполняет реестр ручка→нода (для\n`enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное\nповедение (cleanup живёт на форме).\n\nОбычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту\nфабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента\nмассива, тесты, свой слой сборки.","params":[["args","- Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}"]],"returns":"Типизированная форма с Proxy-доступом к полям {@link FormProxy}","example":"Форма из модели + схемы (эквивалент `createFormFromModel({ 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: Input },\n// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)\n{ value: model.$.profile.name, component: Input },\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,"topics":["api-signatures","formschema","arrays","ui-components","conditional-fields","submit-and-reset","async-preload","validation-strategy","unified-contract"],"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 /**\n * Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт\n * сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.\n * Без него поведение пишет правила в собственный контроллер, который никто не читает.\n */\n schemaController?: SchemaController;\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":"createI18n","kind":"function","signature":"export function createI18n(locale: FormLocale): I18nHandle","summary":"Ручка для локали.","description":"Ручка для локали. Для одного и того же объекта локали возвращается один и тот же объект —\nручку можно держать в зависимостях хуков.","params":[["locale","- Локаль; см. {@link FormLocale}."]],"example":"```ts\nconst i18n = createI18n({ code: 'ru', messages: { 'cart.items': '{count, plural, one{# товар} few{# товара} other{# товаров}}' } });\ni18n.t('cart.items', { count: 3 }); // '3 товара'\ni18n.fileSize(1536); // '1,5 КБ'\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createLegacyForm","kind":"function","signature":"export function createLegacyForm<T>(schema: FormSchema<T>): FormProxy<T>;","summary":"Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.","description":"Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.\n\nЗначения живут в нодах, модели нет: поведение (`defineFormBehavior`) и внешняя валидация\n(`validateModel`) к такой форме не подключаются. Для нового кода — {@link createFormFromModel}\nлибо сборка одним вызовом.","params":[["schema","- Схема полей формы"]],"returns":"Типизированная форма с Proxy-доступом к полям","example":"```typescript\nconst form = createLegacyForm<UserForm>({\n email: { value: '', component: Input },\n password: { value: '', component: Input },\n});\n```","examplesCount":1,"topics":["formschema","unified-contract"],"sourcePath":"src/form/create-form.ts","entries":["."],"package":"@reformer/core"},{"name":"createLocaleLoader","kind":"function","signature":"export function createLocaleLoader(sources: readonly LocaleSource[]): LocaleLoader","summary":"Собирает загрузчик из источников.","description":"Собирает загрузчик из источников. Источники опрашиваются параллельно, а сливаются по порядку:\nпоздний перекрывает ранний. Результат кэшируется по коду языка, идущий запрос не дублируется.\nОтказ любого источника — отказ загрузки; из кэша такой запрос удаляется, и следующая попытка\nпойдёт заново. Источник, которому можно промахнуться, приложение оборачивает само:\n`(code) => source(code).catch(() => null)`.\n\nКод языка у результата — всегда запрошенный: по нему работает `Intl`, и он не должен зависеть\nот того, что написано внутри файла.","params":[["sources","- Источники в порядке возрастания приоритета."]],"example":"```ts\nimport { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';\nimport { loadKitLocale } from '@reformer/ui-kit/locale';\n\nexport const loadLocale = createLocaleLoader([\n loadKitLocale, // подписи кита — чанк нужного языка\n fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера\n]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createLocaleStore","kind":"function","signature":"export function createLocaleStore(\n load: LocaleLoad,\n lang: string,\n options: LocaleStoreOptions = {}\n): LocaleStore","summary":"Создаёт хранилище.","description":"Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —\nиз кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.\nЭто позволяет создавать хранилище прямо в рендере.","params":[["load","- Загрузчик локалей."],["lang","- Начальный язык."],["options","- См. {@link LocaleStoreOptions}."]],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"createMessageFormatter","kind":"function","signature":"export function createMessageFormatter(\n locale: string,\n options: MessageFormatOptions = {}\n): (source: string, values?: MessageValues) => string","summary":"Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.","description":"Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.\nЭто внутренность `I18nService.t()` — сам `t()` добавляет сверху поиск ключа в словаре, маркер\nпропущенного ключа и откат на резервную локаль.","params":[["locale","- Локаль форматтера."],["options","- Настройки; см. {@link MessageFormatOptions}."]],"returns":"Функция `(сообщение, значения) => строка`.","example":"```ts\nconst format = createMessageFormatter('ru');\nformat('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}', { count: 5 });\n// '5 файлов'\n```","examplesCount":1,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"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","unified-contract"],"sourcePath":"src/model/create-model.ts","entries":[".","./model"],"package":"@reformer/core","usage":10},{"name":"createSchemaController","kind":"function","signature":"export function createSchemaController(): SchemaController","summary":"Схема-контроллер одной сборки формы.","description":"Схема-контроллер одной сборки формы.","params":[],"example":"```typescript\nconst controller = createSchemaController();\nconst root = controller.scopeOf(model);\nroot.node('mortgage').setHidden(true);\n\ncontroller.scopeOf(model.items.at(0)); // область строки — свои селекторы\n```","examplesCount":1,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"name":"createSchemaOverrideMaps","kind":"function","signature":"export function createSchemaOverrideMaps(): SchemaOverrideMaps","summary":"Пустое хранилище переопределений области.","description":"Пустое хранилище переопределений области.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"name":"createSchemaScope","kind":"function","signature":"export function createSchemaScope(\n maps: SchemaOverrideMaps = createSchemaOverrideMaps()\n): SchemaScope","summary":"Схема области поверх хранилища: `node(selector)` → управление узлом.","description":"Схема области поверх хранилища: `node(selector)` → управление узлом.","params":[["maps","- Хранилище области; по умолчанию создаётся новое."]],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"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` получает СНАПШОТ модели текущей области (`model.get()`) и вешает ошибку на `sig`.","description":"Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает\nошибку на `sig`.\n\nОбласть — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,\nподключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.\nПоэтому правила подформы пишутся один раз и не знают, где она стоит.","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","unified-contract"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":32},{"name":"DEFAULT_LOCALE","kind":"const","signature":"export const DEFAULT_LOCALE: FormLocale","summary":"Локаль «провайдера нет»: английский, пустой словарь.","description":"Локаль «провайдера нет»: английский, пустой словарь. Каждый пакет при промахе по словарю берёт\nсвою встроенную английскую таблицу, поэтому пустой словарь означает «всё по-английски».","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"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, schema }) => {\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 hideWhen(schema.node('delivery'), () => model.country === '');\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","unified-contract"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core","usage":9},{"name":"defineMessages","kind":"function","signature":"export function defineMessages<D extends Messages>(\n defaults: D\n): {\n msg(key: keyof D & string, values?: Readonly<Record<string, unknown>>): MessageDescriptor;\n}","summary":"`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при компиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе по локали форма покажет его, а не ключ.","description":"`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при\nкомпиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе\nпо локали форма покажет его, а не ключ.","params":[["defaults","- Английский словарь приложения."]],"example":"```ts\nimport appEn from './locales/en.json';\n\nexport const { msg } = defineMessages(appEn);\nmsg('profile.email.label'); // опечатка в ключе — ошибка компиляции\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"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","unified-contract"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"package":"@reformer/core","usage":29},{"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: ArrayBinding<U> | ModelArray<U>,\n itemFn: (item: FormModel<U>) => void\n): void","summary":"Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.","description":"Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.\n\nКолбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот\nкорня прогона.","params":[],"examplesCount":0,"deprecated":"Пишите `applyEach(model.$.<массив>, schema)`.","topics":["api-reference","api-signatures","common-mistakes","complete-import","arrays","extended-mistakes","project-structure","nonexistent-api","array-operations","submit-and-reset","unified-contract"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core"},{"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паттерн, что `revalidateWhen`, но без ручного перечисления зависимостей.\n\nСами массивы обход не посещает — только их элементы. Поле, привязанное к массиву целиком\n(мультивыбор), живёт на сигнале массива; чтобы увидеть и его, берите {@link eachValueSignal}.","params":[],"examplesCount":0,"sourcePath":"src/model/create-model.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"eachValueSignal","kind":"function","signature":"export function eachValueSignal<T>(\n model: FormModel<T>,\n visit: (signal: PathAwareSignal<unknown>) => void\n): void","summary":"Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.","description":"Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.\n\nОтличие от {@link eachLeafSignal} — узлы-массивы: массив бывает одним значением поля\n(`{ value: model.$.tags, component }`), и нода такого поля привязана к сигналу массива, а не\nк сигналам элементов. Обходчику, которому нужна именно НОДА поля (стратегия `blur` подписывается\nна её `touched`), одних листьев мало — он не увидел бы мультивыбор. Так строятся триггеры\n`change`/`blur` стратегий валидации (см. `createFormValidation`).","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","i18n"],"sourcePath":"src/form/validators/email.ts","entries":["./validators"],"package":"@reformer/core","usage":86},{"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","arrays","project-structure","nonexistent-api","conditional-fields","reading-values","array-operations","cycle-detection","reset-when","submit-and-reset","type-safety-recipes","unified-contract"],"sourcePath":"src/form/behaviors/node.ts","entries":[".","./behaviors"],"package":"@reformer/core","usage":16},{"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":"extendLocale","kind":"function","signature":"export function extendLocale(base: FormLocale, patch: Partial<FormLocale>): FormLocale","summary":"Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные поля заменяются.","description":"Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные\nполя заменяются. Исходный объект не меняется.","params":[["base","- Локаль-основа, например поставляемая китом."],["patch","- Свои ключи, переопределения китовых подписей и поля локали."]],"example":"```ts\nimport { ru } from '@reformer/ui-kit/locale/ru';\n\nconst appRu = extendLocale(ru, {\n messages: { 'profile.title': 'Профиль', 'kit.formWizard.submit': 'Отправить' },\n});\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"fetchMessages","kind":"function","signature":"export function fetchMessages(url: (code: string) => string, init?: RequestInit): LocaleSource","summary":"Источник локали по сети: забирает JSON по адресу, построенному из кода языка.","description":"Источник локали по сети: забирает JSON по адресу, построенному из кода языка. Файл может быть\nсловарём (`{ \"profile.title\": \"Профиль\" }`) или локалью целиком (`{ \"code\": \"ru\", \"messages\": … }`).\nОтвет не `2xx` — ошибка загрузки.","params":[["url","- Адрес файла для языка."],["init","- Параметры запроса, передаются в `fetch` как есть."]],"example":"```ts\nfetchMessages((code) => `/locales/${code}.json`);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"FIELD_WRAPPER_ONLY_PROPS","kind":"const","signature":"export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]","summary":"Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у подписи.","description":"Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у\nподписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldAdapter","kind":"interface","signature":"export interface FieldAdapter {\n /** Проп, из которого контрол читает значение (по умолчанию `'value'`). */\n valueProp?: string;\n /** Колбэк, через который контрол эмитит изменение (по умолчанию `'onChange'`). */\n changeProp?: string;\n /**\n * Эмит контрола → значение поля (по умолчанию — как есть). `rest` — прочие props контрола\n * (например, чтобы достать `options` при резолве значения).\n */\n fromEmit?: (arg: unknown, rest: Record<string, unknown>) => unknown;\n /** Значение поля → `valueProp` контрола (коэрсия `null`/`undefined`; по умолчанию — как есть). */\n toValue?: (value: unknown) => unknown;\n /** Проброс blur нестандартным каналом (по умолчанию — `onBlur`). */\n bindBlur?: (onBlur: () => void) => Record<string, unknown>;\n /** Ключи, которые убрать из `componentProps` перед спредом в контрол. */\n strip?: string[];\n /**\n * Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:\n * errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает\n * `useFormControl(control)`.\n */\n passControl?: boolean;\n}","summary":"Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`, DOM-событие в `onChange`, …).","description":"Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,\nDOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.","params":[],"example":"Radix Checkbox\n```ts\nconst checkedAdapter: FieldAdapter = {\nvalueProp: 'checked',\nchangeProp: 'onCheckedChange',\nfromEmit: (c) => c === true,\ntoValue: (v) => v ?? false,\n};\n```","examplesCount":1,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"name":"FieldAdapterCarrier","kind":"interface","signature":"export interface FieldAdapterCarrier {\n reformerAdapter?: FieldAdapter;\n}","summary":"Компонент, объявивший свой диалект статикой.","description":"Компонент, объявивший свой диалект статикой.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"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 * const message = useValidationMessage(); // из '@reformer/core/i18n'\n *\n * return (\n * <ul className=\"error-list\">\n * {errors.map((error, i) => (\n * <li key={i}>{message(error)}</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 * const message = useValidationMessage(); // из '@reformer/core/i18n'\n *\n * return (\n * <div>\n * <input {...props} />\n * {shouldShowError && (\n * <span className=\"error\">{message(errors[0])}</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);\nconst message = useValidationMessage(); // из '@reformer/core/i18n'\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\">{message(state.errors[0])}</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":"FieldHandle","kind":"interface","signature":"export interface FieldHandle {\n /** Сфокусировать поле (делегирует на DOM-элемент контрола). */\n focus(): void;\n /** Снять фокус. */\n blur(): void;\n /** Проскроллить поле в область видимости. */\n scrollIntoView(opts?: ScrollIntoViewOptions): void;\n /** Живой DOM-элемент поля (или `null` до монтирования / для размонтированной ноды). */\n getElement(): HTMLElement | null;\n}","summary":"Базовый императивный контракт поля формы.","description":"Базовый императивный контракт поля формы.\n\nОбёртка поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`)\nстроит его из DOM-узла контрола. Композиты со своим handle (`open`/`close`/`reload`/…) отдают\nего как есть — он расширяет этот контракт.\n\nДостаётся из render-схемы по селектору:\n`schema.node(sel).getRef<FieldHandle>().current?.focus()`.\n\nПокрывает ТОЛЬКО императивные действия. Реактивное состояние (value / disabled / options /\nvalidation) живёт в behaviors и через handle не дублируется.","params":[],"examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.ts","entries":["."],"package":"@reformer/core","usage":4},{"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: Input,\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","arrays","ui-components","type-safety-recipes","i18n"],"sourcePath":"src/form/nodes/field-node.ts","entries":["."],"package":"@reformer/core","usage":6},{"name":"FieldSeam","kind":"interface","signature":"export interface FieldSeam {\n value: unknown;\n onChange: (value: unknown) => void;\n onBlur: () => void;\n}","summary":"Seam поля: текущее значение и value-based колбэки.","description":"Seam поля: текущее значение и value-based колбэки.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"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":"formatPattern","kind":"function","signature":"export function formatPattern(\n pattern: MessagePattern,\n values: MessageValues,\n locale: string,\n options: MessageFormatOptions = {}\n): string","summary":"Собирает строку из разобранного сообщения.","description":"Собирает строку из разобранного сообщения. Значения подставляются как текст и повторно\nне разбираются.","params":[["pattern","- Результат {@link parseMessage}."],["values","- Значения аргументов."],["locale","- Локаль для {@link Intl.PluralRules} и {@link Intl.NumberFormat} (`ru`, `en`, …)."],["options","- Настройки; см. {@link MessageFormatOptions}."]],"returns":"Готовая строка интерфейса.","examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"FormBehavior","kind":"interface","signature":"export interface FormBehavior<T> {\n /**\n * @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.\n * `controller` — схема-контроллер сборки; без него поведение получает собственный, который\n * никто не читает (модель без рендерера).\n */\n __run(model: FormModel<T>, form: FormProxy<T>, controller?: SchemaController): 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","usage":2},{"name":"FormBundle","kind":"interface","signature":"export interface FormBundle<T> {\n model: FormModel<T>;\n form: FormProxy<T>;\n /** Собранная валидация; есть, если в конфиге заданы правила. */\n validation?: FormValidationBundle<T>;\n render: FormRender;\n}","summary":"Результат {@link createForm}.","description":"Результат {@link createForm}.","params":[],"examplesCount":0,"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"FormBundleContext","kind":"const","signature":"export const FormBundleContext","summary":"Контекст сборки.","description":"Контекст сборки. Значение — бандл `createForm`; вне рендерера — `null`.","params":[],"example":"```tsx\n<FormBundleContext.Provider value={bundle}>\n <MyWizard />\n</FormBundleContext.Provider>\n```","examplesCount":1,"sourcePath":"src/platforms/react/form-bundle-context.ts","entries":["."],"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":"FormLocale","kind":"interface","signature":"export interface FormLocale {\n /** Код языка BCP 47 (`en`, `ru`, `pt-BR`): по нему работают `Intl` и множественные формы. */\n readonly code: string;\n /** Словарь сообщений. Ключа нет — пакет подставит встроенный английский. */\n readonly messages: Messages;\n /** Первый день недели календаря. По умолчанию — воскресенье. */\n readonly weekStartsOn?: WeekStart;\n /**\n * Необязательное переопределение: объект локали `react-day-picker` / `date-fns`. Для ядра\n * непрозрачен. Без него даты форматируются через `Intl` по {@link FormLocale.code}.\n */\n readonly dateLocale?: unknown;\n /** Необязательное переопределение: формат даты в токенах `date-fns` для DatePicker. */\n readonly dateFormat?: string;\n}","summary":"Локаль формы.","description":"Локаль формы.","params":[],"example":"```ts\nconst ru: FormLocale = {\n code: 'ru',\n weekStartsOn: 1,\n messages: { 'kit.select.selected': 'Выбрано: {count}' },\n};\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"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","unified-contract"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":50},{"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 = createLegacyForm<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":34},{"name":"FormRender","kind":"interface","signature":"export interface FormRender {\n /** Корневой узел дерева схемы; `undefined`, если схема не задана. */\n readonly tree: FormSchemaNode | undefined;\n /** Схема-контроллер сборки: области корня, строк массивов и подформ. */\n readonly controller: SchemaController;\n /** Управление узлом КОРНЕВОЙ области по его `selector`. */\n node(selector: string): SchemaNodeControl;\n /** Обёртка поля из реестра (JSON-вариант). */\n readonly fieldWrapper?: unknown;\n /** Граница ошибок из реестра (JSON-вариант). */\n readonly errorBoundary?: unknown;\n}","summary":"Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.","description":"Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.","params":[],"examplesCount":0,"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":4},{"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контейнер), передаваемой в `createFormFromModel({ 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: Input },\n address: {\n city: { value: '', component: Input },\n street: { value: '', component: Input },\n },\n items: [{\n title: { value: '', component: Input },\n price: { value: 0, component: Input },\n }],\n};\n```","examplesCount":1,"topics":["api-reference","complete-import","formschema","project-structure"],"sourcePath":"src/form/types/deep-schema.ts","entries":["."],"package":"@reformer/core"},{"name":"FormSchemaNode","kind":"interface","signature":"export interface FormSchemaNode {\n /**\n * Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и\n * соседние ключи:\n * - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;\n * - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;\n * - группа вместе с {@link FormSchemaNode.part} — **подформа**.\n *\n * Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных\n * может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.\n */\n model?: unknown;\n /**\n * Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.\n *\n * @deprecated Пишите `model: model.$.<path>`.\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 /**\n * Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).\n *\n * @deprecated Пишите `model: model.$.<path>`.\n */\n array?: SchemaArrayControl;\n /** Схема элемента массива: под-модель элемента → узел поддерева. */\n item?: (model: any) => FormSchemaNode;\n /**\n * Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к\n * любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.\n * Привязки внутри части идут через `$` полученной под-модели.\n */\n part?: (model: any) => FormSchemaNode;\n /**\n * Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика\n * `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,\n * объявленный в модели (`arrayOf(blank)`), главнее.\n *\n * Тип не различает варианты (union `unknown | (() => unknown)` схлопывается в `unknown`) —\n * рантайм различает по `typeof initialValue === 'function'`.\n */\n initialValue?: unknown;\n /** Свободная вложенность: record-of-fields и произвольные под-узлы. */\n [key: string]: unknown;\n}","summary":"Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })` и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).","description":"Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`\nи рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).\n\nУзел совмещает несколько ролей (различаются рантаймом по форме):\n - **поле** — `{ model: model.$.x, component, componentProps }`;\n - **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;\n - **подформа** — `{ model: model.$.group, part: (model) => узел }`;\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","usage":24},{"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"},{"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":"getFieldAdapter","kind":"function","signature":"export function getFieldAdapter(component: unknown): FieldAdapter | undefined","summary":"Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.","description":"Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.","params":[],"example":"```ts\nCheckbox.reformerAdapter = checkedAdapter;\ngetFieldAdapter(Checkbox); // → checkedAdapter\n```","examplesCount":1,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["."],"package":"@reformer/core"},{"name":"getNodeForSignal","kind":"function","signature":"export function getNodeForSignal(signal: object): FormNode<any> | undefined","summary":"Найти ноду формы по ручке модели.","description":"Найти ноду формы по ручке модели.\n\nИспользуется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем\n(`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма\nещё не построена или эта часть модели в форме не материализована (например, массив без\nitem-схемы).","params":[["signal","- Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`"]],"returns":"Нода формы — поля, группы или массива — либо `undefined`","example":"Роутинг ошибок валидации в ноду поля\n```typescript\nimport { getNodeForSignal } from '@reformer/core';\n\nconst sig = model.$.email;\ngetNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);\n```","examplesCount":2,"related":["registerSignalNode"],"topics":["api-signatures","multi-step","array-operations","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":"Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов).","description":"Текущая область ({ model, form, schema }) активной схемы (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: `createFormFromModel({ model, schema })`); schema-валидация/behavior живут на слое модели\n(`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.","params":[],"example":"```typescript\nconst form = new GroupNode({\n email: { valueSignal: model.$.email, component: Input },\n password: { valueSignal: model.$.password, component: Input },\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":3},{"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":"hideWhen","kind":"function","signature":"export function hideWhen(node: SchemaNodeControl, condition: () => boolean): void","summary":"Скрывать узел, пока условие истинно.","description":"Скрывать узел, пока условие истинно.\n\nУсловие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.\nПринудительное `schema.node(selector).setHidden(...)` главнее условия.","params":[],"example":"```typescript\nhideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');\n```","examplesCount":1,"topics":["project-structure","unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":14},{"name":"I18N_CONTEXT_MARKER","kind":"const","signature":"export const I18N_CONTEXT_MARKER","summary":"Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.","description":"Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/context.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nContextValue","kind":"interface","signature":"export interface I18nContextValue extends I18nHandle {\n /** Идёт загрузка другого языка; на экране пока прежний. Без загрузчика всегда `false`. */\n readonly pending: boolean;\n /** Ошибка последней загрузки языка. */\n readonly error: unknown;\n}","summary":"Что видит компонент: ручка активной локали и состояние её загрузки.","description":"Что видит компонент: ручка активной локали и состояние её загрузки.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/context.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nHandle","kind":"interface","signature":"export interface I18nHandle {\n /** Локаль, по которой работает ручка. */\n readonly locale: FormLocale;\n /** Код языка — то же, что `locale.code`. */\n readonly code: string;\n /** Есть ли ключ в словаре локали (встроенные английские таблицы не учитываются). */\n has(key: string): boolean;\n /**\n * Перевод ключа приложения: словарь локали → `defaultMessage` → встроенная таблица ядра → ключ.\n *\n * @param key - Ключ сообщения.\n * @param values - Значения подстановок.\n * @param defaultMessage - Английский текст на случай, если ключа в локали нет.\n */\n t(key: string, values?: MessageValues, defaultMessage?: string): string;\n /** Число по правилам языка. */\n number(value: number, options?: Intl.NumberFormatOptions): string;\n /** Дата по правилам языка. */\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /** Размер файла: `1536` → `1.5 KB` (`1,5 КБ` в русской локали). База — 1024. */\n fileSize(bytes: number): string;\n}","summary":"Ручка локали: перевод и форматирование по одному языку.","description":"Ручка локали: перевод и форматирование по одному языку.","params":[],"examplesCount":0,"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nLoadingProviderProps","kind":"interface","signature":"export interface I18nLoadingProviderProps {\n /** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */\n lang: string;\n /** Загрузчик локалей — обычно результат `createLocaleLoader`. */\n load: LocaleLoad;\n /**\n * Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр\n * мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`\n * до монтирования), первый рендер синхронный и `fallback` не показывается.\n */\n fallback?: ReactNode;\n /** Отказ загрузки. Язык на экране при этом не меняется. */\n onError?: (error: unknown, lang: string) => void;\n children?: ReactNode;\n}","summary":"Режим «провайдер грузит сам».","description":"Режим «провайдер грузит сам».","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nProvider","kind":"function","signature":"export function I18nProvider(props: I18nProviderProps): ReactElement | null","summary":"Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.","description":"Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.\nБез провайдера компоненты говорят встроенным английским.\n\nПри смене `lang` на экране остаётся прежний язык, пока новый не загрузится (`useI18n().pending`\n— для индикатора). При быстром переключении применяется только последний запрос. Вложенный\nпровайдер заменяет локаль для своего поддерева целиком.","params":[],"example":"Провайдер грузит локаль сам\n```tsx\nconst [lang, setLang] = useState('ru');\n\n<I18nProvider lang={lang} load={loadLocale}>\n<LangSwitch value={lang} onChange={setLang} />\n<CreditForm />\n</I18nProvider>\n```","examplesCount":2,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nProviderProps","kind":"type","signature":"export type I18nProviderProps = I18nStaticProviderProps | I18nLoadingProviderProps;","summary":"","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nStaticProviderProps","kind":"interface","signature":"export interface I18nStaticProviderProps {\n /** Локаль. Смена объекта переключает язык сразу. */\n locale: FormLocale;\n children?: ReactNode;\n}","summary":"Режим «готовая локаль».","description":"Режим «готовая локаль».","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./i18n"],"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":"isMessageDescriptor","kind":"function","signature":"export function isMessageDescriptor(value: unknown): value is MessageDescriptor","summary":"Является ли значение описателем сообщения.","description":"Является ли значение описателем сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"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":"isValueSignal","kind":"function","signature":"export function isValueSignal(value: unknown): value is Signal<unknown>","summary":"«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив дерева `model.$` (значение — массив целиком, `.value` читается и пишется).","description":"«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив\nдерева `model.$` (значение — массив целиком, `.value` читается и пишется).\n\nГруппа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой\n`createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —\nвместо голого `value instanceof Signal`, который узел-массив не проходит.","params":[],"example":"```typescript\nconst model = createModel({ name: '', tags: [] as string[], address: { city: '' } });\nisValueSignal(model.$.name); // true — лист\nisValueSignal(model.$.tags); // true — массив как значение\nisValueSignal(model.$.address); // false — группа\n```","examplesCount":1,"sourcePath":"src/model/model-signals-proxy.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"loadCoreLocale","kind":"const","signature":"export const loadCoreLocale","summary":"Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.","description":"Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.","params":[],"example":"```ts\nconst loadLocale = createLocaleLoader([loadCoreLocale, fetchMessages((code) => `/locales/${code}.json`)]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/locale/index.ts","entries":["./locale"],"package":"@reformer/core"},{"name":"LocaleIssue","kind":"interface","signature":"export interface LocaleIssue {\n readonly key: string;\n /**\n * - `syntax` — сообщение не разбирается;\n * - `missing` — ключ есть в образце, но его нет в словаре;\n * - `extra` — ключа нет в образце;\n * - `arguments` — набор подстановок не совпадает с образцом.\n */\n readonly kind: 'syntax' | 'missing' | 'extra' | 'arguments';\n readonly message: string;\n}","summary":"Что не так со словарём.","description":"Что не так со словарём.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleLoad","kind":"interface","signature":"export interface LocaleLoad {\n (code: string): Promise<FormLocale>;\n peek?(code: string): FormLocale | undefined;\n}","summary":"Функция загрузки локали.","description":"Функция загрузки локали. `peek` — необязательный синхронный кэш (есть у `createLocaleLoader`).","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleLoader","kind":"interface","signature":"export interface LocaleLoader {\n /** Загрузить локаль языка. Повторный вызов возвращает тот же промис и ту же локаль. */\n (code: string): Promise<FormLocale>;\n /** Уже загруженная локаль или `undefined`. Позволяет провайдеру отрисоваться синхронно. */\n peek(code: string): FormLocale | undefined;\n /** То же, что вызов загрузчика; имя для чтения в коде старта приложения. */\n preload(code: string): Promise<FormLocale>;\n}","summary":"Загрузчик локалей с кэшем.","description":"Загрузчик локалей с кэшем.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleSource","kind":"type","signature":"export type LocaleSource = (code: string) => LocaleSourceResult | Promise<LocaleSourceResult>;","summary":"Источник локали.","description":"Источник локали. Получает код языка, отдаёт локаль или словарь — сразу или промисом.\n`null`/`undefined` означает «для этого языка у меня ничего нет» и ошибкой не считается.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleSourceResult","kind":"type","signature":"export type LocaleSourceResult = Partial<FormLocale> | Messages | null | undefined;","summary":"Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.","description":"Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.","params":[],"examplesCount":0,"sourcePath":"src/i18n/loader.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStore","kind":"interface","signature":"export interface LocaleStore {\n getState(): LocaleStoreState;\n subscribe(listener: () => void): () => void;\n /** Запросить язык. Повторный запрос того же языка, пока он грузится или уже показан, — no-op. */\n setLang(lang: string): void;\n}","summary":"Хранилище активной локали.","description":"Хранилище активной локали.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStoreOptions","kind":"interface","signature":"export interface LocaleStoreOptions {\n /** Вызывается при отказе загрузки языка, который всё ещё запрошен. */\n readonly onError?: (error: unknown, lang: string) => void;\n}","summary":"Настройки хранилища.","description":"Настройки хранилища.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocaleStoreState","kind":"interface","signature":"export interface LocaleStoreState {\n /** Запрошенный язык. Пока `pending`, может отличаться от языка `locale`. */\n readonly lang: string;\n /** Локаль, которая сейчас на экране; `null` — ни одна ещё не загружена. */\n readonly locale: FormLocale | null;\n /** Идёт загрузка запрошенного языка. */\n readonly pending: boolean;\n /** Ошибка последней загрузки; сбрасывается следующей удачной. */\n readonly error: unknown;\n}","summary":"Состояние хранилища.","description":"Состояние хранилища. Объект неизменяемый: новая ссылка — новое состояние.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale-store.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"LocalizableText","kind":"type","signature":"export type LocalizableText = string | MessageDescriptor;","summary":"Текст, который можно задать строкой или описателем.","description":"Текст, который можно задать строкой или описателем.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"makeElementFieldHandle","kind":"function","signature":"export function makeElementFieldHandle(el: RefObject<HTMLElement | null>): FieldHandle","summary":"Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.","description":"Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.\nВсе вызовы null-safe: до монтирования (`el.current === null`) — no-op, без исключений.","params":[],"examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.ts","entries":["."],"package":"@reformer/core"},{"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":24},{"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":14},{"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,"topics":["i18n"],"sourcePath":"src/form/validators/max-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":11},{"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","arrays","type-safety-recipes"],"sourcePath":"src/form/validators/max-length.ts","entries":["./validators"],"package":"@reformer/core","usage":29},{"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,"topics":["i18n"],"sourcePath":"src/form/validators/max-total-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"MESSAGE_DESCRIPTOR","kind":"const","signature":"export const MESSAGE_DESCRIPTOR: unique symbol","summary":"Бренд описателя.","description":"Бренд описателя. `Symbol.for` — чтобы описатель узнавался и через границу двух копий модуля\n(разные чанки, разные версии пакета на странице).","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"messageArguments","kind":"function","signature":"export function messageArguments(\n pattern: MessagePattern,\n out: Set<string> = new Set()\n): Set<string>","summary":"Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.","description":"Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.","params":[["pattern","- Результат `parseMessage`."]],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageBranches","kind":"type","signature":"export type MessageBranches = ReadonlyMap<string, MessagePattern>;","summary":"Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.","description":"Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageDescriptor","kind":"interface","signature":"export interface MessageDescriptor {\n readonly [MESSAGE_DESCRIPTOR]: true;\n /** Ключ сообщения в словаре приложения. */\n readonly key: string;\n /** Значения подстановок. */\n readonly values?: Readonly<Record<string, unknown>>;\n /** Английский текст на случай, если ключа в локали нет. */\n readonly defaultMessage?: string;\n}","summary":"Описатель сообщения.","description":"Описатель сообщения. Намеренно без ключей `value`, `array`, `item`, `component`: обход схемы в\n`createForm` узнаёт по ним узел, а описатель узлом не является.","params":[],"examplesCount":0,"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageFormatOptions","kind":"interface","signature":"export interface MessageFormatOptions {\n /**\n * Что подставить вместо аргумента, которого нет в значениях (или чьё значение не число там, где\n * ожидалось число). По умолчанию — заметный маркер `⟦name⟧`, тот же приём, что и для пропущенного\n * ключа: пропуск должен попадаться разработчику, а не пользователю.\n */\n readonly onMissingArgument?: (name: string) => string;\n}","summary":"Настройки форматирования.","description":"Настройки форматирования.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageNode","kind":"type","signature":"export type MessageNode =\n | { readonly kind: 'text'; readonly value: string }\n | { readonly kind: 'arg'; readonly name: string }\n | { readonly kind: 'number'; readonly name: string }\n | { readonly kind: 'plural'; readonly name: string; readonly branches: MessageBranches }\n | { readonly kind: 'select'; readonly name: string; readonly branches: MessageBranches };","summary":"Узел разобранного сообщения.","description":"Узел разобранного сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessagePattern","kind":"type","signature":"export type MessagePattern = readonly MessageNode[];","summary":"Разобранное сообщение — плоская последовательность узлов.","description":"Разобранное сообщение — плоская последовательность узлов.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"Messages","kind":"type","signature":"export type Messages = Readonly<Record<string, string>>;","summary":"Плоский словарь: ключ → сообщение в формате ICU (см.","description":"Плоский словарь: ключ → сообщение в формате ICU (см. `message-format.ts`).","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageSyntaxError","kind":"class","signature":"export class MessageSyntaxError extends Error { /* … */ }","summary":"Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.","description":"Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"MessageValues","kind":"type","signature":"export type MessageValues = Readonly<Record<string, unknown>>;","summary":"Значения аргументов сообщения.","description":"Значения аргументов сообщения.","params":[],"examplesCount":0,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":40},{"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,"topics":["i18n"],"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","i18n"],"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 /**\n * Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком\n * (ручка значения массива — см. {@link ModelArraySignals}). Для пути группы — `undefined`.\n */\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 * Добавить элемент в конец (значение элемента целиком). Без значения — элемент по шаблону\n * массива (`arrayOf(blank)` в модели); шаблона нет — ошибка.\n */\n push(item?: U): void;\n /** Вставить элемент по индексу. Без значения — элемент по шаблону, как у {@link push}. */\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Обычно создаётся не напрямую, а `createFormFromModel({ 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: Input },\nqty: { value: item.$.qty, component: Input },\n});\n\nconst form = createFormFromModel({\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"},{"name":"ModelArraySignals","kind":"type","signature":"export type ModelArraySignals<U, V> = Signal<V> & {\n /** Путь массива в модели (dot-нотация). */\n readonly __path: string;\n readonly length: number;\n readonly [index: number]: ModelSignalNode<U>;\n};","summary":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length` и сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава — push/removeAt/move).","description":"Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`\nи сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава —\npush/removeAt/move).\n\nВ отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это\nделает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),\nправило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением\nполя: мультивыбор, теги, список файлов.\n\n`V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`\nв рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих\nслучаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).","params":[],"examplesCount":0,"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":1},{"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":"modelOf","kind":"function","signature":"export function modelOf<V extends object | null | undefined>(handle: { peek(): V }): ModelOf<V>","summary":"Value-фасад по ручке дерева `model.$`: группа → под-модель {@link FormModel}, массив → {@link ModelArray}.","description":"Value-фасад по ручке дерева `model.$`: группа → под-модель {@link FormModel}, массив →\n{@link ModelArray}. Обратная операция к `model.$`: `modelOf(model.$.address) === model.address`.\n\nРучка находит фасад по ИДЕНТИЧНОСТИ, а не по пути. Путь для этого не годится: он абсолютный\n(`items.0.phones`) и меняется при перестановке строк, а области поведения и формы строк\nвложенные — поиск по пути от под-модели строки ничего не находит.\n\nЛист ручкой контейнера не является — функция бросает. В частности, группа или массив,\nсозданные из начального `null`, в модели — лист: вид узла фиксируется при создании.","params":[["handle","- Узел-группа или узел-массив дерева `model.$`."]],"returns":"Под-модель группы либо фасад массива.","example":"```typescript\nconst model = createModel({ address: { city: '' }, phones: [{ number: '' }] });\n\nmodelOf(model.$.address).city = 'Казань'; // под-модель: чтение, запись, get/set/patch\nmodelOf(model.$.phones).push({ number: '+7' }); // фасад массива: мутации и обход\nmodelOf(model.$.phones[0]).number; // строка массива — тоже группа\n```","examplesCount":1,"topics":["array-operations"],"sourcePath":"src/model/model-value-proxy.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelOf","kind":"type","signature":"export type ModelOf<V> =\n NonNullable<V> extends ReadonlyArray<infer U> ? ModelArray<U> : FormModel<NonNullable<V>>;","summary":"Value-фасад для ручки {@link modelOf}: массив → {@link ModelArray}, объект → под-модель {@link FormModel}.","description":"Value-фасад для ручки {@link modelOf}: массив → {@link ModelArray}, объект → под-модель\n{@link FormModel}.","params":[],"examplesCount":0,"sourcePath":"src/model/model-value-proxy.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":"msg","kind":"function","signature":"export function msg(\n key: string,\n values?: Readonly<Record<string, unknown>>,\n defaultMessage?: string\n): MessageDescriptor","summary":"Создаёт описатель сообщения.","description":"Создаёт описатель сообщения.","params":[["key","- Ключ в словаре приложения."],["values","- Значения подстановок."],["defaultMessage","- Английский текст на случай, если ключа в локали нет."]],"example":"```ts\n{ value: model.$.email, component: Input, componentProps: { label: msg('profile.email.label') } }\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/descriptor.ts","entries":["./i18n"],"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: Input,\n * validators: [required, email]\n * });\n *\n * // GroupNode\n * const group = factory.createNode({\n * email: { value: '', component: Input },\n * password: { value: '', component: Input }\n * });\n *\n * // ArrayNode (объект)\n * const array = factory.createNode({\n * schema: { title: { value: '', component: Input } },\n * initialItems: [{ title: 'Item 1' }]\n * });\n *\n * // ArrayNode (массив) - новый формат\n * const array2 = factory.createNode([\n * { title: { value: '', component: Input } }, // 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: Input }); // → FieldNode<string>\n```","examplesCount":1,"sourcePath":"src/form/factories/node-factory.ts","entries":["."],"package":"@reformer/core"},{"name":"NodeLifecycleHooks","kind":"interface","signature":"export interface NodeLifecycleHooks {\n /** Срабатывает один раз при mount узла. Может вернуть cleanup-функцию. */\n onMount?: () => void | (() => void);\n /** Срабатывает один раз при unmount узла. */\n onUnmount?: () => void;\n}","summary":"Хуки жизненного цикла узла схемы.","description":"Хуки жизненного цикла узла схемы. Каждый хук опционален; повторная регистрация перезаписывает\nпредыдущее значение.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.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","i18n"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":21},{"name":"onComponentEvent","kind":"function","signature":"export function onComponentEvent(\n node: SchemaNodeControl,\n event: string,\n handler: (...args: any[]) => any\n): void","summary":"Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).","description":"Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).\n\nОбработчик получает те же аргументы, что и исходный проп компонента, и перекрывает одноимённый\nпроп из `componentProps`.","params":[],"example":"```typescript\nonComponentEvent(schema.node('wizard'), 'onSubmit', async () => {\n await submitApplication(model.get());\n});\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":12},{"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":"onInit","kind":"function","signature":"export function onInit(_node: SchemaNodeControl, fn: () => void): void","summary":"Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла.","description":"Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный\nхук, способный повлиять на первый рендер, — внутри можно править пропсы через\n`schema.node(selector).patchProps({ … })`.\n\nК монтированию узла не привязан; аргумент `node` принимается ради симметрии с\n{@link onMount} / {@link onUnmount}.","params":[],"example":"```typescript\nonInit(schema.node('summary'), () => {\n schema.node('summary').patchProps({ currency: detectCurrency() });\n});\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":5},{"name":"onMount","kind":"function","signature":"export function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void","summary":"Хук после первого монтирования узла.","description":"Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.","params":[],"example":"```typescript\nonMount(schema.node('data-boundary'), () => void loadApplication());\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":6},{"name":"onUnmount","kind":"function","signature":"export function onUnmount(node: SchemaNodeControl, fn: () => void): void","summary":"Хук перед размонтированием узла.","description":"Хук перед размонтированием узла.","params":[],"example":"```typescript\nonUnmount(schema.node('wizard'), () => saveDraft(model.get()));\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":2},{"name":"parseMessage","kind":"function","signature":"export function parseMessage(source: string): MessagePattern","summary":"Разбирает сообщение в дерево.","description":"Разбирает сообщение в дерево. Бросает {@link MessageSyntaxError} на ошибке синтаксиса и на\n`plural`/`select` без ветки `other`.","params":[["source","- Текст сообщения из словаря."]],"returns":"Разобранное сообщение для {@link formatPattern}.","example":"```ts\nparseMessage('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}');\n```","examplesCount":1,"sourcePath":"src/i18n/message-format.ts","entries":["./i18n"],"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","arrays","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","array-operations","transform-value"],"sourcePath":"src/form/validators/phone.ts","entries":["./validators"],"package":"@reformer/core","usage":22},{"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: object, node: FormNode<any>): void","summary":"Связать ручку модели с её нодой формы.","description":"Связать ручку модели с её нодой формы.\n\nВызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,\nгруппы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.","params":[["signal","- Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`"],["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":"renderEffect","kind":"function","signature":"export function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void","summary":"Реактивный эффект, живущий, пока схема области смонтирована рендерером.","description":"Реактивный эффект, живущий, пока схема области смонтирована рендерером.\n\nВ отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов\n(`schema.node(selector).getRef().current`). Может вернуть функцию очистки.","params":[],"example":"```typescript\nconst wizard = schema.node('wizard').getRef<FormWizardHandle>();\nrenderEffect(schema, () => {\n if (model.loanType === 'mortgage') wizard.current?.goToStep(1);\n});\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":3},{"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","i18n","unified-contract"],"sourcePath":"src/form/validators/required.ts","entries":["./validators"],"package":"@reformer/core","usage":125},{"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":"ResolvedSchema","kind":"interface","signature":"export interface ResolvedSchema {\n /** Корневой узел дерева. */\n tree: FormSchemaNode;\n /** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */\n fieldWrapper?: unknown;\n /** Граница ошибок — компонент, которым рендерер оборачивает дерево. */\n errorBoundary?: unknown;\n}","summary":"Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до рендерера вместе с ним.","description":"Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до\nрендерера вместе с ним.","params":[],"examplesCount":0,"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core"},{"name":"resolveLocalized","kind":"function","signature":"export function resolveLocalized<T>(value: T, i18n: I18nHandle): T","summary":"Заменяет описатели сообщений на строки по активной локали.","description":"Заменяет описатели сообщений на строки по активной локали. Обходит массивы и простые\nобъекты-литералы — подписи опций, заголовки шагов; сигналы, React-элементы, функции и\nэкземпляры классов (узлы формы) возвращает как есть. Поддерево без описателей сохраняет ссылку:\nесли раскрывать было нечего, возвращается тот же объект.","params":[["value","- Пропсы, массив или одиночное значение."],["i18n","- Ручка активной локали (`createI18n` / `useI18n`)."]],"example":"```ts\nresolveLocalized({ label: msg('profile.email.label'), options: [{ value: 'a', label: msg('opt.a') }] }, i18n);\n// { label: 'Почта', options: [{ value: 'a', label: 'Вариант А' }] }\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/resolve-localized.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"resolveValidationError","kind":"function","signature":"export function resolveValidationError(error: ValidationError, i18n: I18nHandle): string","summary":"Отображаемый текст ошибки валидации.","description":"Отображаемый текст ошибки валидации. Порядок источников — в шапке модуля.","params":[["error","- Ошибка от правила: `code`, `message`, `messageKey`, `params`."],["i18n","- Ручка активной локали (`createI18n` / `useI18n`)."]],"example":"```ts\nresolveValidationError({ code: 'minLength', message: '', params: { minLength: 8 } }, i18n);\n// 'Enter at least 8 characters' · в русской локали — 'Не меньше 8 символов'\n\nresolveValidationError({ code: 'required', message: 'Укажите телефон' }, i18n);\n// 'Укажите телефон' — явное сообщение автора важнее словаря\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"package":"@reformer/core"},{"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, scope, root)`: модель текущей\nобласти (в корне прогона — вся модель) и корень прогона.","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":"SchemaController","kind":"interface","signature":"export interface SchemaController {\n /**\n * Область под-модели: корня сборки, строки массива или группы подформы. Создаётся при первом\n * обращении; повторное обращение отдаёт ту же область.\n */\n scopeOf(subModel: object): SchemaScope;\n}","summary":"Схема-контроллер сборки: области по под-моделям.","description":"Схема-контроллер сборки: области по под-моделям.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaNodeControl","kind":"interface","signature":"export interface SchemaNodeControl {\n /** Принудительно скрыть или показать узел, минуя условие `hideWhen`. */\n setHidden(value: boolean): this;\n /** Убрать принудительное значение — снова действует условие `hideWhen`. */\n resetHidden(): this;\n /** Подмешать объект в `componentProps` узла. */\n patchProps(partial: Record<string, unknown>): this;\n /** Убрать подмешанные пропсы. */\n resetProps(): this;\n /**\n * Ref на компонент узла. Создаётся один раз на селектор; рендерер вешает его на компонент.\n * Компонент обязан принимать `ref`.\n */\n getRef<H>(): { current: H | null };\n /** @internal Селектор узла. */\n __selector: string;\n /** @internal Хранилище области. */\n __overrideMaps: SchemaOverrideMaps;\n}","summary":"Управление одним узлом схемы.","description":"Управление одним узлом схемы. Получается через `schema.node(selector)`.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":[".","./behaviors"],"package":"@reformer/core"},{"name":"SchemaOverrideMaps","kind":"interface","signature":"export interface SchemaOverrideMaps {\n hiddenOverrides: Map<string, boolean | null>;\n propsOverrides: Map<string, Record<string, unknown> | null>;\n refRegistry: Map<string, { current: any }>;\n /** Условия скрытия узлов: selector → реактивное условие. */\n conditionRegistry: Map<string, () => boolean>;\n /** Реактивные эффекты, живущие, пока область смонтирована (`renderEffect`). */\n effectRegistry: Array<() => void | (() => void)>;\n /** Обработчики событий компонентов: selector → { имя пропа → обработчик }. */\n callbackRegistry: Map<string, Map<string, (...args: any[]) => any>>;\n /** Хуки жизненного цикла узла: selector → { onMount, onUnmount }. */\n lifecycleRegistry: Map<string, NodeLifecycleHooks>;\n /** Общий счётчик изменений области. Узлы на него не подписываются — см. `versionFor`. */\n version: Signal<number>;\n /**\n * Версия-сигнал одного селектора (создаётся лениво). Узел подписывается только на сигнал СВОЕГО\n * селектора, поэтому `setHidden` / `patchProps` уведомляют один узел, а не всё дерево.\n */\n versionFor(selector: string): Signal<number>;\n}","summary":"Хранилище переопределений одной области схемы.","description":"Хранилище переопределений одной области схемы. Ключ каждой карты — `selector` узла.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaResolver","kind":"interface","signature":"export interface SchemaResolver {\n resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;\n}","summary":"Реестр, умеющий превратить документ схемы в дерево.","description":"Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра\nесть сторонние реализации, и ядро документ само не разбирает.","params":[],"examplesCount":0,"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaScope","kind":"interface","signature":"export interface SchemaScope {\n /** Управление узлом области по его `selector`. */\n node(selector: string): SchemaNodeControl;\n /** @internal Хранилище области — его читает рендерер. */\n __overrideMaps: SchemaOverrideMaps;\n}","summary":"Схема одной области: адресует узлы по `selector`.","description":"Схема одной области: адресует узлы по `selector`.","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":[".","./behaviors"],"package":"@reformer/core"},{"name":"schemaSubtree","kind":"function","signature":"export function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N","summary":"Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.","description":"Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.","params":[["builder","- Билдер поддерева: `item` узла-массива или `part` узла-подформы."],["subModel","- Под-модель строки массива или группы."]],"returns":"Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.","example":"```typescript\nconst row = model.items.at(0);\nschemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/form/schema-subtree.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":"translateBuiltin","kind":"function","signature":"export function translateBuiltin(\n locale: FormLocale,\n builtin: Messages,\n key: string,\n values?: MessageValues\n): string","summary":"Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам ключ.","description":"Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам\nключ. Английская таблица форматируется по английским правилам — её множественные формы и\nгруппировка чисел не зависят от активного языка.","params":[["locale","- Активная локаль."],["builtin","- Встроенная английская таблица пакета-владельца ключа."],["key","- Ключ сообщения."],["values","- Значения подстановок."]],"examplesCount":0,"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":"unknownSchemaSelectors","kind":"function","signature":"export function unknownSchemaSelectors(maps: SchemaOverrideMaps, tree: unknown): string[]","summary":"Селекторы, на которые в области записаны правила, но которых нет в её дереве.","description":"Селекторы, на которые в области записаны правила, но которых нет в её дереве. Такое правило\nничего не делает — сборка и рендерер в dev предупреждают о нём.\n\nRef в проверку не входит: его разрешено брать по абсолютному пути модели\n(`phones.0.number`), а такой строки при сборке может ещё не быть.","params":[["maps","- Хранилище области."],["tree","- Дерево (поддерево) области."]],"returns":"Селекторы с правилами, не найденные в дереве.","example":"```typescript\nconst scope = controller.scopeOf(model);\nunknownSchemaSelectors(scope.__overrideMaps, tree); // ['mortgage'] — узла с таким selector нет\n```","examplesCount":1,"sourcePath":"src/form/schema-controller.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":"useFieldHandleRef","kind":"function","signature":"export function useFieldHandleRef<H = FieldHandle>(\n outerRef: Ref<H> | undefined\n): RefObject<unknown>","summary":"Ref для контрола поля + императивный handle для потребителя.","description":"Ref для контрола поля + императивный handle для потребителя.\n\nВозвращённый ref вешается на контрол. На `outerRef` публикуется:\n- handle самого контрола, если тот его реализует (`useImperativeHandle` композита);\n- иначе — базовый {@link FieldHandle}, построенный из DOM-узла контрола.\n\nХук вызывается безусловно; вешать ref на контрол стоит только при заданном `outerRef` —\nтак function-компонент без ref-поддержки не получает лишний ref.","params":[["outerRef","- ref потребителя (из `forwardRef` или `schema.node(sel).getRef()`)."]],"returns":"ref, который нужно передать контролу.","examplesCount":0,"topics":["ui-components"],"sourcePath":"src/platforms/react/field-binding/field-handle.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","unified-contract"],"sourcePath":"src/platforms/react/hooks/use-form-bundle.ts","entries":["."],"package":"@reformer/core","usage":36},{"name":"useFormBundleContext","kind":"function","signature":"export function useFormBundleContext<T = unknown>(): FormBundle<T> | null","summary":"Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера (например, визард в разметке React-руками получает форму и валидацию пропсами).","description":"Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера\n(например, визард в разметке React-руками получает форму и валидацию пропсами).","params":[],"example":"```tsx\nfunction StepFooter() {\n const bundle = useFormBundleContext<CreditForm>();\n return <button onClick={() => bundle?.validation?.validateAll()}>Проверить</button>;\n}\n```","examplesCount":1,"topics":["unified-contract"],"sourcePath":"src/platforms/react/form-bundle-context.ts","entries":["."],"package":"@reformer/core"},{"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 { useValidationMessage } from '@reformer/core/i18n';\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// Текст ошибки даёт резолвер: у правил без своего сообщения `message` пуст.\nconst message = useValidationMessage();\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 {message(errors[0])}\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","i18n"],"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":"useI18n","kind":"function","signature":"export function useI18n(): I18nContextValue","summary":"Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние загрузки языка.","description":"Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние\nзагрузки языка. Без провайдера — английский с пустым словарём.","params":[],"example":"```tsx\nfunction LangBadge() {\n const { code, pending, t } = useI18n();\n return <span aria-busy={pending}>{t('app.language')}: {code}</span>;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"useMessages","kind":"function","signature":"export function useMessages<D extends Messages>(\n defaults: D\n): (key: keyof D & string, values?: MessageValues) => string","summary":"Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую таблицу этого пакета.","description":"Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую\nтаблицу этого пакета. Так пакет говорит по-английски без провайдера и не зависит от того, знает\nли локаль его ключи. Тип ключа выводится из таблицы — опечатка в ключе не скомпилируется.\n\n`defaults` должен быть стабильной ссылкой (импорт JSON-модуля), иначе переводчик будет\nпересоздаваться на каждый рендер.","params":[["defaults","- Встроенная английская таблица пакета."]],"returns":"Функция `(ключ, значения?) => строка`; её идентичность меняется вместе с локалью.","example":"```tsx\nimport en from './locale/en.json';\n\nconst useKitMessages = () => useMessages(en);\n\nfunction ClearButton() {\n const t = useKitMessages();\n return <button aria-label={t('kit.select.clear')} />;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"useValidationMessage","kind":"function","signature":"export function useValidationMessage(): (error: ValidationError) => string","summary":"Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` → словарь локали по коду → встроенный английский → код.","description":"Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` →\nсловарь локали по коду → встроенный английский → код. Идентичность функции меняется вместе с\nлокалью, поэтому показанные ошибки переводятся при смене языка без повторной валидации.\n\n`FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете ошибки\nсвоим компонентом.","params":[],"example":"```tsx\nfunction FieldErrors({ control }: { control: FieldNode<string> }) {\n const message = useValidationMessage();\n const { errors } = useFormControl(control);\n return <>{errors.map((error) => <span key={error.code}>{message(error)}</span>)}</>;\n}\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"validate","kind":"function","signature":"export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void","summary":"Синхронные правила поля.","description":"Синхронные правила поля.\n\nПоле — лист либо массив целиком: `model.$.<массив>` — такая же ручка значения, и правило\nполучает массив (мультивыбор, теги, список файлов).","params":[],"example":"```ts\nvalidate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);\nvalidate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение\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","i18n","unified-contract"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":146},{"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":"validateLocale","kind":"function","signature":"export function validateLocale(messages: Messages, reference?: Messages): LocaleIssue[]","summary":"Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена подстановок совпадают с ним.","description":"Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена\nподстановок совпадают с ним. Нужна там, где компилятор не поможет: словарь пришёл по сети или\nнаписан вне репозитория. В рантайме вызывать не обязательно — битое или пропущенное сообщение\nи так откатится на встроенный английский; проверка нужна тестам и CI.","params":[["messages","- Проверяемый словарь."],["reference","- Образец, обычно английский словарь того же пакета."]],"returns":"Список расхождений; пустой — словарь в порядке.","example":"```ts\nexpect(validateLocale(ruJson, enJson)).toEqual([]);\n```","examplesCount":1,"topics":["i18n"],"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"},{"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":27},{"name":"ValidateOptions","kind":"interface","signature":"export interface ValidateOptions {\n /** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст\n * берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в\n * `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —\n * для локализуемого текста используйте {@link ValidateOptions.messageKey}. */\n message?: string;\n /**\n * Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в\n * активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.\n *\n * @example\n * ```ts\n * required({ messageKey: 'profile.name.required', message: 'Name is required' });\n * ```\n */\n messageKey?: 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","unified-contract"],"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 /**\n * Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку\n * тогда даёт резолвер по `code` (словарь локали `validation.<code>`).\n */\n message: string;\n /** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */\n messageKey?: 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":23},{"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":4},{"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},{"name":"WeekStart","kind":"type","signature":"export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;","summary":"День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.","description":"День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.","params":[],"examplesCount":0,"sourcePath":"src/i18n/locale.ts","entries":["./i18n"],"package":"@reformer/core"}]}
|