@reformer/core 11.0.0-beta.1 → 11.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -68
- package/dist/behaviors-value-D05z3my6.js +109 -0
- package/dist/behaviors.d.ts +1 -1
- package/dist/behaviors.js +376 -211
- package/dist/create-model-DPSG3I0O.js +30 -0
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/en-HP8XEych.js +44 -0
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +84 -0
- package/dist/form/behaviors/context.d.ts +56 -0
- package/dist/form/behaviors/index.d.ts +30 -0
- package/dist/form/behaviors/internals.d.ts +41 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
- package/dist/form/behaviors/operators.d.ts +59 -0
- package/dist/form/behaviors/schema-operators.d.ts +83 -0
- package/dist/form/behaviors/types.d.ts +37 -0
- package/dist/form/create-form.d.ts +59 -70
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +153 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +23 -24
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +159 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +27 -22
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +21 -85
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -57
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +147 -46
- package/dist/form/types/validation-schema.d.ts +25 -17
- package/dist/form/validation/context.d.ts +67 -0
- package/dist/form/validation/index.d.ts +24 -28
- package/dist/form/validation/operators.d.ts +101 -0
- package/dist/form/validation/run.d.ts +120 -0
- package/dist/form/validation/strategy.d.ts +89 -0
- package/dist/form/validation/types.d.ts +83 -0
- package/dist/form/validators/date-utils.d.ts +42 -0
- package/dist/form/validators/email.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
- package/dist/form/validators/future-date.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
- package/dist/form/validators/integer.d.ts +22 -0
- package/dist/form/validators/is-date.d.ts +24 -0
- package/dist/form/validators/is-number.d.ts +25 -0
- package/dist/form/validators/max-age.d.ts +28 -0
- package/dist/form/validators/max-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
- package/dist/form/validators/max-files.d.ts +26 -0
- package/dist/form/validators/max-length.d.ts +33 -0
- package/dist/form/validators/max-total-file-size.d.ts +26 -0
- package/dist/form/validators/max.d.ts +26 -0
- package/dist/form/validators/min-age.d.ts +27 -0
- package/dist/form/validators/min-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
- package/dist/form/validators/min-files.d.ts +27 -0
- package/dist/form/validators/min-length.d.ts +33 -0
- package/dist/form/validators/min.d.ts +26 -0
- package/dist/form/validators/multiple-of.d.ts +25 -0
- package/dist/form/validators/non-negative.d.ts +22 -0
- package/dist/form/validators/non-zero.d.ts +22 -0
- package/dist/form/validators/past-date.d.ts +23 -0
- package/dist/form/validators/pattern.d.ts +26 -0
- package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
- package/dist/form/validators/required.d.ts +29 -0
- package/dist/form/validators/url.d.ts +45 -0
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +49 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +46 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +136 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +49 -0
- package/dist/i18n/translator.d.ts +61 -0
- package/dist/i18n/validation-message.d.ts +53 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +86 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +942 -2601
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +45 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +44 -0
- package/dist/locale/ru.json +45 -0
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/{state → model}/behaviors-value.d.ts +24 -6
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
- package/dist/{state → model}/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +42 -0
- package/dist/model/model-nodes.d.ts +190 -0
- package/dist/model/model-signals-proxy.d.ts +63 -0
- package/dist/model/model-value-proxy.d.ts +84 -0
- package/dist/{state → model}/safe-effect.d.ts +50 -2
- package/dist/model/types.d.ts +239 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +27 -0
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +58 -36
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
- package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +18 -0
- package/dist/platforms/react/i18n/hooks.d.ts +65 -0
- package/dist/platforms/react/i18n/provider.d.ts +59 -0
- package/dist/platforms/react/index.d.ts +30 -0
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +1 -2
- package/dist/safe-effect-BazLOv1g.js +669 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-DzvnsWLh.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +11 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/future-date.js +10 -13
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-age.js +8 -12
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-date.js +10 -13
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max-length.js +7 -10
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-age.js +10 -11
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min-length.js +4 -7
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/multiple-of.js +11 -10
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-negative.js +2 -5
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/past-date.js +10 -13
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/phone.js +10 -11
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators/url.js +48 -17
- package/dist/validators.d.ts +1 -1
- package/dist/validators.js +86 -108
- package/docs/llms/01-api-reference.md +60 -34
- package/docs/llms/02-quick-start.md +112 -74
- package/docs/llms/03-api-signatures.md +369 -148
- package/docs/llms/04-common-patterns.md +58 -56
- package/docs/llms/05-common-mistakes.md +69 -44
- package/docs/llms/06-troubleshooting.md +14 -9
- package/docs/llms/07-complete-import.md +74 -43
- package/docs/llms/08-form-types.md +11 -4
- package/docs/llms/09-formschema.md +132 -84
- package/docs/llms/10-arrays.md +141 -61
- package/docs/llms/11-async-watchfield.md +2 -2
- package/docs/llms/13-multi-step.md +134 -63
- package/docs/llms/14-extended-mistakes.md +20 -17
- package/docs/llms/15-project-structure.md +224 -70
- package/docs/llms/16-ui-components.md +86 -46
- package/docs/llms/17-nonexistent-api.md +217 -32
- package/docs/llms/18-conditional-fields.md +71 -32
- package/docs/llms/19-reading-values.md +45 -4
- package/docs/llms/20-compute-vs-watch.md +25 -10
- package/docs/llms/21-array-operations.md +76 -26
- package/docs/llms/22-cycle-detection.md +1 -1
- package/docs/llms/23-copy-from.md +2 -2
- package/docs/llms/24-sync-fields.md +6 -5
- package/docs/llms/25-reset-when.md +14 -7
- package/docs/llms/26-transform-value.md +4 -4
- package/docs/llms/27-revalidate-when.md +16 -15
- package/docs/llms/28-submit-and-reset.md +278 -128
- package/docs/llms/29-async-preload.md +41 -31
- package/docs/llms/30-type-safety-recipes.md +105 -51
- package/docs/llms/31-async-validator-debounce.md +96 -53
- package/docs/llms/33-validation-strategy.md +178 -66
- package/docs/llms/34-i18n.md +298 -0
- package/docs/llms/35-unified-contract.md +309 -0
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +396 -0
- package/llms-index.json +1 -0
- package/llms.txt +10690 -3089
- package/package.json +48 -9
- package/dist/behaviors-node-DGEFMTQu.js +0 -18
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/error-handler.d.ts +0 -166
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -188
- package/dist/form/form-submitter.d.ts +0 -121
- package/dist/form/nodes/array-node.d.ts +0 -276
- package/dist/form/nodes/field-node.d.ts +0 -294
- package/dist/form/nodes/form-node.d.ts +0 -326
- package/dist/form/nodes/group-node.d.ts +0 -284
- package/dist/form/nodes/model-array-node.d.ts +0 -121
- package/dist/form/status-machine.d.ts +0 -115
- package/dist/form/types/deep-schema.d.ts +0 -107
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -45
- package/dist/form/validation/validators/date-utils.d.ts +0 -30
- package/dist/form/validation/validators/email.d.ts +0 -23
- package/dist/form/validation/validators/future-date.d.ts +0 -23
- package/dist/form/validation/validators/integer.d.ts +0 -22
- package/dist/form/validation/validators/is-date.d.ts +0 -23
- package/dist/form/validation/validators/is-number.d.ts +0 -24
- package/dist/form/validation/validators/max-age.d.ts +0 -25
- package/dist/form/validation/validators/max-date.d.ts +0 -25
- package/dist/form/validation/validators/max-files.d.ts +0 -26
- package/dist/form/validation/validators/max-length.d.ts +0 -27
- package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
- package/dist/form/validation/validators/max.d.ts +0 -25
- package/dist/form/validation/validators/min-age.d.ts +0 -25
- package/dist/form/validation/validators/min-date.d.ts +0 -25
- package/dist/form/validation/validators/min-files.d.ts +0 -26
- package/dist/form/validation/validators/min-length.d.ts +0 -27
- package/dist/form/validation/validators/min.d.ts +0 -25
- package/dist/form/validation/validators/multiple-of.d.ts +0 -24
- package/dist/form/validation/validators/non-negative.d.ts +0 -22
- package/dist/form/validation/validators/non-zero.d.ts +0 -22
- package/dist/form/validation/validators/past-date.d.ts +0 -23
- package/dist/form/validation/validators/pattern.d.ts +0 -33
- package/dist/form/validation/validators/required.d.ts +0 -32
- package/dist/form/validation/validators/url.d.ts +0 -43
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validation-strategy.d.ts +0 -55
- package/dist/form/validators.d.ts +0 -11
- package/dist/form-model-DYCzGGKF.js +0 -286
- package/dist/state/index.d.ts +0 -31
- package/dist/state/subscription-manager.d.ts +0 -180
- package/dist/state/types.d.ts +0 -144
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
package/llms-index.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion":1,"package":"@reformer/core","version":"6.0.0","topics":[{"id":"api-reference","file":"01-api-reference.md","title":"api-reference","purpose":"Значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Схема, правила и поведение привязываются к ручкам модели (`model.$.field`), а не к строковым путям.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"1. API Reference","slug":"api-reference"}],"terms":[["reformer",32],["core",32],["model",8],["react",7],["validation",6],["value",6],["useformcontrolvalue",5],["behaviors",5],["формы",5],["bundle",5],["loantype",5],["createform",4],["validate",4],["useformcontrol",4],["поля",4],["только",4],["type",4],["модели",3],["правила",3],["field",3],["validationissue",3],["computefrom",3],["copyfrom",3],["required",3],["min",3],["validators",3],["корень",3],["сигналы",3],["null",3],["flow",3],["string",3],["const",3],["control",3],["critically",2],["important",2],["живут",2],["createmodel",2],["поверх",2],["схема",2],["поведение",2],["ruleoptions",2],["submitresult",2],["отправка",2],["definevalidation",2],["apply",2],["applyeach",2],["validatemodel",2],["runvalidation",2],["раннер",2],["rule",2],["validationresult",2],["типы",2],["usearraylength",2],["useformflow",2],["связка",2],["useissuemessage",2],["defineissuemessages",2],["watchfield",2],["transformvalue",2],["resetwhen",2],["syncfields",2],["revalidatewhen",2],["примитивы",2],["над",2],["сигналами",2],["поведения",2],["email",2],["defineformbehavior",2],["compute",2],["enablewhen",2],["disablewhen",2],["без",2],["сборки",2],["локали",2],["операторы",2],["cleanup",2],["возвращают",2],["правил",2],["valid",2],["invalid",2],["замечания",2],["code",2],["params",2],["issue",2],["messages",2],["опциональные",2],["number",2],["или",2],["issues",2],["disabled",2],["напрямую",2],["длина",2],["массива",2],["шагов",2],["correct",2]]},{"id":"quick-start","file":"02-quick-start.md","title":"quick-start","purpose":"**Модель данных** — источник истины значений. Схема — дерево узлов, привязанных к ручкам модели (`model: model.$.field`). Сборка идёт ОДНИМ вызовом `createForm`: он создаёт модель, строит форму, запускает поведение и собирает уровень формы — проверку и отправку. Правил в схеме нет — они живут в отдельной схеме `defineValidation` из `@reformer/core/validation` и уходят в сборку полем `validation`; запускает их `bundle.submission.submit(handler)` / `bundle.validate()`. Текста у правил нет: правило несёт код, строку по коду даёт словарь формы (`defineIssueMessages`).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"1.5 QUICK START - Minimal Working Form","slug":"quick-start-minimal-working-form"}],"terms":[["model",27],["form",20],["type",17],["reformer",14],["email",14],["name",12],["const",11],["component",10],["componentprops",10],["item",10],["properties",10],["import",9],["from",9],["label",8],["formfield",8],["core",8],["react",7],["required",7],["createform",6],["submit",6],["validate",6],["строки",6],["один",5],["kit",5],["input",5],["модели",5],["формы",5],["validation",5],["bundle",5],["formmodel",5],["contactform",5],["форма",5],["propertyitem",5],["schema",4],["control",4],["field",4],["правил",4],["схеме",4],["нет",4],["formschemanode",4],["useformbundle",4],["children",4],["description",4],["estimatedvalue",4],["options",3],["jsx",3],["props",3],["модель",3],["схема",3],["форму",3],["уровень",3],["definevalidation",3],["сборку",3],["submission",3],["код",3],["defineissuemessages",3],["formgroup",3],["validationmessagesprovider",3],["button",3],["string",3],["узел",3],["initial",3],["раз",3],["contact",3],["submitted",3],["testid",3],["под",3],["push",3],["driven",2],["его",2],["placeholder",2],["без",2],["свои",2],["find_recipe",2],["topic",2],["дерево",2],["узлов",2],["сборка",2],["одним",2],["вызовом",2],["строит",2],["запускает",2],["они",2],["отдельной",2],["handler",2],["правило",2],["несёт",2],["словарь",2],["typescript",2],["interface",2],["contactschema",2],["your",2],["отдельный",2],["текст",2],["contactvalidation",2],["code",2],["contactmessages",2],["укажите",2],["createcontactform",2],["function",2],["зовёт",2],["handlesubmit",2],["async",2],["event",2],["при",2],["обработчик",2],["outcome",2],["values",2],["send",2],["return",2],["components",2],["formstepprops",2],["сборки",2],["вне",2],["отправка",2],["только",2],["flow",2],["arrays",2],["одной",2],["arrayof",2],["fieldarray",2],["inputnumber",2],["selectasync",2],["textarea",2],["apartment",2],["house",2],["myform",2],["blankproperty",2],["propertyrow",2],["length",2],["число",2],["array",2]]},{"id":"api-signatures","file":"03-api-signatures.md","title":"api-signatures","purpose":"// Сборка одним вызовом: модель, форма, уровень формы и дерево для рендера createForm<T extends object>({ initial | model, schema?: (model) => tree, // билдер; JSON — документ + registry behavior?, // defineFormBehavior(...) validation?, // ValidationDefinition<T> | { rules?, extras?, strategy?, debounce? } flow?, // { steps: [{ id, rules? }] } — id = selector узла шага seed?, setup?, }): FormBundle<T> // bundle = { model, form, render, flow, validating, submission, validate, validateStep, dispose } // (+ служебное validation: { rules, controller } | undefined — его армирует useFormBundle) // await bundle.validate() → ValidationResult (вся форма; замечания показываются) // await bundle.validateStep('loan') → ValidationResult (правила шага; неизвестный шаг — исключение) // await bundle.submission.submit(handler) → SubmitResult<R> (один прогон; обработчик — только при valid) // bundle.submission.submitting → ReadonlySignal<boolean> (работает обработчик) // bundle.flow?.next() / previous() / goTo(id) → FlowTransition // bundle.render.tree → CompiledSchema (см. 37-compiled-schema.md) // bundle.dispose() → снять сборку: поведение, подписки нод, живую проверку; // идущие проверки — cancelled; модель остаётся; идемпотентно // useFormBundle(() => createForm(…)) снимает сборку сам — при размонтировании компонента","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"2. API SIGNATURES","slug":"api-signatures"}],"terms":[["model",73],["options",36],["void",32],["number",29],["ruleoptions",28],["validate",26],["readonly",26],["value",25],["boolean",23],["null",22],["form",21],["правила",21],["reformer",20],["core",20],["readonlysignal",18],["string",18],["поля",17],["email",17],["const",17],["typescript",16],["формы",16],["schema",16],["validation",16],["loan",16],["rule",15],["required",15],["bundle",14],["flow",13],["undefined",13],["только",13],["при",13],["без",13],["над",13],["unknown",12],["await",11],["validationissue",11],["from",11],["signal",11],["promise",10],["type",10],["схема",10],["import",10],["cross",10],["группы",9],["interface",9],["code",9],["definevalidation",9],["модель",8],["createform",8],["validationresult",8],["замечания",8],["поведение",8],["модели",8],["нет",8],["строки",8],["поле",8],["min",8],["схемы",8],["типом",8],["apply",8],["signup",8],["confirm",8],["массива",7],["rules",7],["шага",7],["valid",7],["сборки",7],["check",7],["formgroup",7],["formnode",7],["async",7],["правило",7],["date",7],["applyeach",7],["validationdefinition",6],["submit",6],["проверка",6],["result",6],["ноды",6],["invalid",6],["index",6],["массив",6],["validators",6],["внутри",6],["либо",6],["операторы",6],["true",6],["error",6],["password",6],["extends",5],["initial",5],["length",5],["behavior",5],["submission",5],["issues",5],["status",5],["замечание",5],["age",5],["asyncrule",5],["получает",5],["поведения",5],["enablewhen",5],["copyfrom",5],["object",4],["formmodel",4],["field",4],["pathawaresignal",4],["схеме",4],["снимок",4],["dev",4],["item",4],["форма",4],["extras",4],["strategy",4],["selector",4],["validatestep",4],["прогон",4],["код",4],["через",4],["pending",4],["touched",4],["ручке",4],["formcontrol",4],["component",4],["значения",4],["полями",4],["sig",4],["валидации",4],["loantype",4],["amount",4],["области",4],["validatewhen",4],["сбой",4],["coborrower",4],["income",4],["behaviors",4],["cleanup",4],["compute",4],["onchange",4],["price",4],["quantity",4],["country",4],["computefrom",4],["revalidatewhen",4],["target",4],["api",3],["значений",3],["reset",3],["ручка",3],["group",3],["move",3],["для",3],["defineformbehavior",3],["debounce",3],["steps",3],["dispose",3],["controller",3],["его",3],["handler",3],["обработчик",3],["работает",3],["сборку",3],["нод",3],["cancelled",3],["остаётся",3],["снимает",3],["пока",3],["идёт",3],["false",3],["values",3],["форму",3],["замечаний",3],["dirty",3],["enable",3],["record",3],["city",3],["items",3],["max",3],["minlength",3],["строка",3],["url",3],["validator",3],["factories",3],["даёт",3],["bytes",3],["count",3],["как",3],["params",3],["словарь",3],["под",3],["issue",3],["messages",3],["loanschema",3],["функция",3],["моделью",3],["abortsignal",3],["несколькими",3],["handle",3],["validateasync",3],["раннер",3]]},{"id":"common-patterns","file":"04-common-patterns.md","title":"common-patterns","purpose":"Значения живут в модели (`model.$.field`), поведение (`defineFormBehavior`) работает на живых сигналах, валидация — отдельная схема (`defineValidation`, прогон по требованию через раннер). Слои раздельны: схема формы правил НЕ несёт.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"3. COMMON PATTERNS","slug":"common-patterns"}],"terms":[["model",39],["reformer",14],["core",14],["const",14],["import",12],["from",12],["cross",12],["validation",12],["form",11],["defineformbehavior",10],["field",9],["typescript",9],["value",9],["definevalidation",8],["myform",8],["initialpayment",7],["validate",7],["compute",6],["async",6],["bundle",6],["правила",6],["правило",6],["null",6],["модели",5],["валидация",5],["схема",5],["при",5],["propertyvalue",5],["onchange",5],["поля",5],["region",5],["submit",5],["required",5],["поведение",4],["через",4],["behaviors",4],["behavior",4],["loantype",4],["computefrom",4],["price",4],["options",4],["react",4],["email",4],["снимок",4],["schema",4],["flow",4],["validationissue",4],["validatewhen",4],["revalidatewhen",4],["отдельная",3],["формы",3],["reset",3],["enablewhen",3],["внутри",3],["для",3],["вне",3],["signal",3],["return",3],["await",3],["без",3],["values",3],["только",3],["сборки",3],["validatemodel",3],["поток",3],["схемы",3],["min",3],["initialpaymentvsproperty",3],["rule",3],["validateadultage",3],["требованию",2],["раннер",2],["conditional",2],["auto",2],["оператор",2],["поведения",2],["поле",2],["initial",2],["mortgage",2],["computed",2],["сигнал",2],["изменении",2],["источников",2],["источники",2],["кросс",2],["total",2],["quantity",2],["qty",2],["fullname",2],["группы",2],["personaldata",2],["boolean",2],["effect",2],["abortsignal",2],["city",2],["updatecomponentprops",2],["cities",2],["debounce",2],["напрямую",2],["submission",2],["разносит",2],["замечания",2],["нодам",2],["event",2],["outcome",2],["multi",2],["step",2],["rules",2],["над",2],["validators",2],["type",2],["читается",2],["как",2],["code",2],["строки",2],["мост",2],["strategy",2],["age",2],["new",2],["date",2],["getfullyear",2]]},{"id":"common-mistakes","file":"05-common-mistakes.md","title":"common-mistakes","purpose":"- Модель/форма/типы (`createForm`, `FormModel`, `FormSchemaNode`, `ValidationIssue`, `FormFlow`) — из `@reformer/core`. - Хуки (`useFormBundle`, `useFormControl`, `useFormFlow`, `useIssueMessage`, …) и `I18nProvider` — из `@reformer/core/react`. - Схема валидации (`defineValidation` + операторы `validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`) и раннер `validateModel` — из `@reformer/core/validation`. `cross` берётся из аргумента схемы. - Чистые фабрики валидаторов (`required`/`min`/`email`/…) — из `@reformer/core/validators`. - Словарь текстов замечаний (`defineIssueMessages`, `resolveIssueMessage`) — из `@reformer/core/i18n`. - Декларативный DSL поведения (`defineFormBehavior` + операторы) — из `@reformer/core/behaviors`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"4. COMMON MISTAKES","slug":"common-mistakes"}],"terms":[["email",23],["model",22],["reformer",18],["core",18],["form",15],["type",14],["value",14],["const",12],["required",10],["validate",9],["cross",9],["amount",9],["typescript",8],["import",8],["from",8],["формы",7],["correct",6],["useformcontrolvalue",6],["нет",6],["loantype",6],["null",6],["ctx",6],["interface",6],["createform",5],["definevalidation",5],["validation",5],["api",5],["price",5],["node",5],["useformcontrol",4],["операторы",4],["validateasync",4],["validators",4],["dsl",4],["behaviors",4],["formgroup",4],["enablewhen",4],["computefrom",4],["модели",4],["submit",4],["через",4],["wrong",4],["total",4],["bundle",4],["rate",4],["string",4],["модель",3],["форма",3],["validationissue",3],["схема",3],["validatemodel",3],["схемы",3],["min",3],["defineissuemessages",3],["отдельный",3],["max",3],["compute",3],["watchfield",3],["для",3],["значения",3],["ноды",3],["control",3],["path",3],["quantity",3],["values",3],["оператор",3],["нод",3],["myform",3],["code",3],["await",3],["export",3],["field",3],["maxamount",3],["instanceof",3],["formcontrol",3],["rule",2],["formmodel",2],["formschemanode",2],["хуки",2],["useformbundle",2],["react",2],["валидации",2],["validatewhen",2],["apply",2],["applyeach",2],["раннер",2],["берётся",2],["аргумента",2],["словарь",2],["i18n",2],["декларативный",2],["поведения",2],["defineformbehavior",2],["createmodel",2],["слой",2],["validationdefinition",2],["поведение",2],["copyfrom",2],["onchange",2],["примитивы",2],["над",2],["снимок",2],["returns",2],["object",2],["сигналы",2],["пути",2],["форму",2],["старый",2],["удалён",2],["city",2],["boolean",2],["country",2],["qty",2],["валидаторы",2],["узел",2],["валидация",2],["узла",2],["json",2],["уровнем",2],["submission",2],["schema",2],["message",2],["внутри",2],["emailschema",2],["async",2],["signal",2],["res",2],["free",2],["taken",2],["правило",2],["setfieldvalue",2],["функция",2],["amountvsmax",2],["или",2],["formarray",2],["поля",2],["index",2],["signature",2],["propertyitem",2],["propertytype",2],["description",2],["record",2],["unknown",2],["extends",2],["тип",2],["вид",2],["isformcontrol",2],["isformarray",2]]},{"id":"troubleshooting","file":"06-troubleshooting.md","title":"troubleshooting","purpose":"","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"5. TROUBLESHOOTING","slug":"troubleshooting"}],"terms":[["required",6],["model",5],["flow",5],["field",4],["reformer",4],["core",4],["без",4],["нет",3],["поле",3],["goto",3],["not",2],["ruleoptions",2],["текст",2],["правил",2],["code",2],["phone",2],["defineissuemessages",2],["messages",2],["вместо",2],["api",2],["useformcontrolvalue",2],["createform",2],["cycle",2],["через",2],["модели",2],["form",2],["submit",2],["await",2],["schema",2],["код",2],["области",2],["шага",2],["вперёд",2]]},{"id":"complete-import","file":"07-complete-import.md","title":"complete-import","purpose":"// Привязка к React — хуки, контекст сборки, связка поля с контролом, провайдер локали — из /react import { useFormBundle, useFormControl, useFormControlValue, useArrayLength, useFormFlow, // снимок потока шагов для рендера useFormValidation, // стратегия без сборки: submit() — попытка отправки, validate() — проверка useIssueMessage, // текст замечания: словарь формы → локаль → английский → код I18nProvider, } from '@reformer/core/react'; import type { FieldControlState } from '@reformer/core/react';","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"Import Patterns","slug":"import-patterns"}],"terms":[["import",12],["reformer",12],["core",12],["from",11],["model",8],["без",7],["сборки",7],["react",6],["массив",6],["type",6],["createform",5],["interface",5],["string",5],["формы",4],["bundle",4],["модели",4],["шагов",4],["для",4],["validate",4],["rules",4],["apply",4],["поток",3],["поля",3],["submit",3],["submission",3],["нода",3],["под",3],["форм",3],["validationissue",3],["схемы",3],["правила",3],["validation",3],["applyeach",3],["promise",3],["behaviors",3],["typescript",2],["модель",2],["форма",2],["типы",2],["нет",2],["createmodel",2],["createformfrommodel",2],["dispose",2],["отправка",2],["дерево",2],["compiledschema",2],["локаль",2],["данных",2],["render",2],["formgroup",2],["formarray",2],["массива",2],["code",2],["params",2],["поле",2],["схема",2],["итог",2],["операторы",2],["раннер",2],["сбор",2],["разнос",2],["validationresult",2],["value",2],["null",2],["cross",2],["validators",2],["i18n",2],["computefrom",2],["copyfrom",2],["принимают",2],["значений",2],["export",2]]},{"id":"form-types","file":"08-form-types.md","title":"form-types","purpose":"Форму данных объявляй через `interface` или `type`-alias — оба варианта работают одинаково (см. `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",4],["myform",4],["форму",3],["name",3],["address",3],["city",3],["items",3],["definition",2],["данных",2],["interface",2],["работают",2],["createmodel",2],["fields",2],["email",2],["phone",2],["age",2],["number",2],["status",2],["active",2],["objects",2],["street",2],["arrays",2],["model",2],["значения",2],["массив",2],["вычисляемые",2]]},{"id":"formschema","file":"09-formschema.md","title":"formschema","purpose":"Схема — **дерево узлов**. Узел привязан к части модели ключом `model` и держит UI-конфиг (`component` / `componentProps`). Значения принадлежат модели. Правил валидации в схеме НЕТ — они живут в отдельной схеме `defineValidation` из `@reformer/core/validation`.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"8. SCHEMA FORMAT (CRITICALLY IMPORTANT)","slug":"schema-format-critically-important"}],"terms":[["model",58],["component",19],["componentprops",14],["item",14],["form",13],["schema",12],["label",12],["address",11],["const",11],["children",10],["input",10],["модели",9],["reformer",9],["formschemanode",9],["part",9],["под",9],["name",9],["core",8],["узла",8],["value",8],["typescript",7],["import",7],["from",7],["поле",6],["selector",6],["поля",6],["myform",6],["formmodel",6],["дерево",5],["вид",5],["массив",5],["email",5],["city",5],["узел",4],["узлы",4],["disabled",4],["массива",4],["строки",4],["type",4],["сборка",4],["формы",4],["createformfrommodel",4],["ноды",4],["модель",4],["items",4],["нет",3],["definevalidation",3],["validation",3],["тип",3],["ключи",3],["или",3],["форм",3],["подформа",3],["группа",3],["string",3],["для",3],["без",3],["age",3],["agree",3],["status",3],["active",3],["раз",3],["через",3],["либо",3],["опционально",3],["formcontrol",3],["formgroup",3],["схема",2],["узлов",2],["части",2],["значения",2],["схеме",2],["phones",2],["placeholder",2],["options",2],["постоянная",2],["блокировка",2],["вычисляемые",2],["имя",2],["поведения",2],["node",2],["произвольными",2],["ключами",2],["dev",2],["ключ",2],["enable",2],["группы",2],["field",2],["createmodel",2],["validate",2],["required",2],["inputnumber",2],["selectasync",2],["checkboxwithlabel",2],["myvalidation",2],["inactive",2],["данных",2],["угодно",2],["разметку",2],["один",2],["группе",2],["street",2],["zip",2],["itemrow",2],["значение",2],["createform",2],["форма",2],["bundle",2],["tree",2],["behavior",2],["mybehavior",2],["rules",2],["flow",2],["steps",2],["доступ",2],["push",2],["которого",2],["compiled",2],["isformcontrol",2]]},{"id":"arrays","file":"10-arrays.md","title":"arrays","purpose":"Массивы объектов — **model-owned**: данные принадлежат модели (`model.arrayField` — это `ModelArray<Item>` с реактивными `push`/`removeAt`/`length`). В схеме массив объявляется узлом `{ model: model.$.<path>, item: (itemModel) => узел }`, где `item` строит разметку одной строки из её под-модели (`FormModel<Item>`).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":["[03-api-signatures.md](./03-api-signatures.md) — сигнатуры нод `form.<array>` / `ModelArray`, правила строк `applyEach`","[37-compiled-schema.md](./37-compiled-schema.md) — область строки `#/items[<ключ>]` и `keyAt`","CDK / ui-kit field-array (`FieldArray.Root`, `FieldArraySection`) — `find_recipe(topic=\"field-array\")`"],"sections":[{"heading":"9. ARRAY SCHEMA FORMAT","slug":"array-schema-format"},{"heading":"See also","slug":"see-also"}],"terms":[["model",67],["item",41],["items",34],["form",29],["массив",23],["строки",15],["const",13],["name",12],["component",11],["array",10],["модели",10],["узел",10],["myform",10],["null",10],["schema",9],["под",9],["price",9],["typescript",8],["fieldarray",8],["tags",8],["формы",8],["length",7],["массива",7],["схема",7],["applyeach",7],["push",6],["схеме",6],["formmodel",6],["type",6],["formschemanode",6],["reformer",6],["string",6],["для",6],["children",6],["cross",6],["строк",6],["схемы",6],["control",6],["index",6],["arrayof",5],["field",5],["ручка",5],["группа",5],["поле",5],["значения",5],["или",5],["через",5],["properties",5],["modelarray",4],["removeat",4],["createformfrommodel",4],["from",4],["input",4],["kit",4],["поля",4],["validate",4],["получает",4],["contact",4],["правила",4],["fieldarraysection",4],["map",4],["keyat",4],["при",4],["div",4],["button",4],["это",3],["path",3],["одной",3],["import",3],["createmodel",3],["core",3],["blank",3],["список",3],["шаблон",3],["formarray",3],["как",3],["массивом",3],["лист",3],["ошибка",3],["значение",3],["форм",3],["без",3],["над",3],["value",3],["required",3],["запись",3],["группе",3],["строке",3],["ноду",3],["валидации",3],["definevalidation",3],["cdk",3],["root",3],["плоские",3],["insertat",3],["clear",3],["ключ",3],["react",3],["hasitems",3],["names",3],["массивы",2],["данные",2],["itemmodel",2],["inputnumber",2],["добавить",2],["blankitem",2],["разметка",2],["itemrow",2],["форма",2],["является",2],["одно",2],["путь",2],["ничего",2],["целиком",2],["правило",2],["pathawaresignal",2],["только",2],["рантайме",2],["так",2],["контейнер",2],["есть",2],["address",2],["тип",2],["его",2],["пишется",2],["contactrow",2],["phonerow",2],["внутри",2],["operations",2],["три",2],["validation",2],["схемой",2],["оператором",2],["область",2],["снимок",2],["propertyrules",2],["itemcomponent",2],["нода",2],["тот",2],["move",2],["swap",2],["модель",2],["ноды",2],["insert",2],["число",2],["key",2],["behavior",2],["выше",2],["formgroup",2],["usearraylength",2],["return",2],["formfield",2],["onclick",2],["remove",2],["add",2],["find_recipe",2],["topic",2],["носитель",2],["api",2],["signatures",2],["compiled",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],["model",6],["async",4],["reformer",4],["core",4],["signal",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":"Многошаговая форма — **поток шагов** сборки: `createForm({ flow: { steps: [{ id, rules }] } })`. Каждый шаг — идентификатор и своя `ValidationDefinition<Form>` (обычная функция `({ model, cross }) => void`, обёрнутая `defineValidation`). Идентификатор шага — `selector` узла шага в схеме. Правила вне шагов (межшаговые) — `validation: { extras }`: они проверяются только целиком, при отправке. Валидация живёт ОТДЕЛЬНО от схемы формы: узел схемы (TS или JSON) правил не несёт.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"12. MULTI-STEP FORM VALIDATION","slug":"multi-step-form-validation"}],"terms":[["flow",28],["loan",24],["form",19],["bundle",14],["const",14],["model",11],["steps",10],["шага",10],["validation",9],["шагов",9],["next",9],["validate",9],["submit",9],["поток",8],["сборки",8],["правила",8],["reformer",8],["без",8],["validatestep",7],["submission",7],["from",7],["await",7],["rules",6],["шаг",6],["definevalidation",6],["только",6],["import",6],["через",6],["formwizard",6],["button",6],["selector",5],["схеме",5],["extras",5],["правил",5],["core",5],["validatemodel",5],["required",5],["createform",4],["формы",4],["полный",4],["useformbundle",4],["createloanform",4],["loanrules",4],["шаги",4],["current",4],["validationdefinition",3],["void",3],["схемы",3],["сборка",3],["goto",3],["applicant",3],["confirm",3],["applicantrules",3],["один",3],["sendloan",3],["validating",3],["проверка",3],["шаге",3],["status",3],["reason",3],["визард",3],["useformflow",3],["onclick",3],["apply",3],["step_rules",3],["true",3],["step",2],["идентификатор",2],["узла",2],["целиком",2],["при",2],["узел",2],["previous",2],["проверку",2],["отправку",2],["handler",2],["замечания",2],["поля",2],["текущего",2],["typescript",2],["react",2],["узлы",2],["всей",2],["раз",2],["validationresult",2],["всех",2],["набор",2],["для",2],["остаётся",2],["переход",2],["async",2],["после",2],["нет",2],["kit",2],["его",2],["tsx",2],["function",2],["send",2],["outcome",2],["форму",2],["return",2],["onsubmit",2],["component",2],["children",2],["сам",2],["навигация",2],["визарда",2],["isfirst",2],["islast",2],["div",2],["disabled",2],["min",2],["personaldata",2],["fullrules",2],["touch",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",34],["compute",11],["form",10],["cross",8],["reformer",7],["core",7],["field1",7],["const",6],["apply",5],["applyeach",5],["typescript",5],["import",5],["from",5],["validation",5],["validate",5],["defineformbehavior",4],["row",4],["field",4],["схемы",4],["value",4],["root",4],["schema",4],["transformvalue",3],["цикл",3],["prefix",3],["firstname",3],["cycle",3],["detected",3],["без",3],["формы",3],["правило",3],["validatemodel",3],["validators",3],["validateformmodel",3],["field2",3],["null",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],["схеме",2],["валидации",2],["поле",2],["check",2],["модели",2],["definevalidation",2],["required",2],["validationissue",2],["revalidatewhen",2],["modelvalidator",2],["scope",2],["legacyrule",2],["code",2],["legacyschema",2],["field1lessthanfield2",2],["замечания",2],["bundle",2],["submit",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` (form behavior: model operators AND schema-node rules such as `hideWhen` / `onComponentEvent`), `form.validation.ts` (validation rules), and the optional `form.flow.ts` (the steps of a wizard) and `form.messages.ts` (the issue-message dictionary); every other file has no prefix. There is one behavior per form: the former render-layer file `form.render.ts` (and `renderer.behavior.ts`) is retired — its node rules move into `form.behavior.ts`. The old `renderer.schema.*` / `validation.ts` names, and the retired `form.render.ts` / `wizard.tsx` / `renderer.wizard.tsx`, are accepted with a warning. No folders until the form grows (see \"Scaling up\" below). Arrays are declared in `form.schema.ts` and rendered with `FieldArray` / `FieldArraySection` — no per-step component files.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"14. PROJECT STRUCTURE (COLOCATION)","slug":"project-structure-colocation"}],"terms":[["the",110],["form",70],["model",45],["and",34],["step",34],["rules",30],["steps",29],["flow",27],["one",24],["behavior",23],["schema",22],["validation",22],["application",19],["forms",18],["export",15],["creditapplicationform",13],["from",13],["const",13],["layout",12],["file",12],["renderer",12],["wizard",11],["type",11],["tsx",9],["per",9],["with",9],["folders",9],["credit",9],["import",9],["reformer",9],["selector",9],["index",8],["component",8],["bundle",8],["values",8],["submit",8],["only",8],["core",8],["all",7],["node",7],["messages",7],["are",7],["createform",7],["title",7],["for",7],["module",6],["folder",6],["whole",6],["entry",6],["array",6],["extras",6],["same",6],["loantype",6],["fields",6],["sub",6],["react",6],["content",6],["icon",6],["flat",5],["every",5],["concern",5],["root",5],["its",5],["hidewhen",5],["code",5],["not",5],["creditapplicationflow",5],["loan",5],["contacts",5],["default",4],["minimalist",4],["single",4],["inline",4],["rule",4],["names",4],["operators",4],["optional",4],["issue",4],["layer",4],["declared",4],["files",4],["call",4],["formwizard",4],["types",4],["interface",4],["rows",4],["tree",4],["componentprops",4],["compute",4],["enablewhen",4],["each",4],["defineissuemessages",4],["texts",4],["api",4],["number",4],["when",4],["computed",4],["formmodel",4],["nodes",4],["children",4],["address",4],["formvalidation",4],["loanrules",4],["contactsrules",4],["creditapplicationvalidation",4],["formflowconfig",4],["page",4],["what",4],["does",4],["sections",4],["handler",4],["written",4],["dictionary",3],["render",3],["scaling",3],["below",3],["components",3],["reusable",3],["name",3],["value",3],["dictionaries",3],["createmodel",3],["initial",3],["blank",3],["defineformbehavior",3],["data",3],["options",3],["load",3],["slug",3],["that",3],["loanamount",3],["createcreditapplicationmodel",3],["creditapplicationschema",3],["live",3],["part",3],["creditapplicationbehavior",3],["mortgage",3],["definevalidation",3],["required",3],["outside",3],["submission",3],["question",3],["how",3],["create",3],["conditions",3],["shared",3],["loading",3],["list",3],["way",3],["server",3],["json",3],["once",3],["mcp",3],["structure",2],["role",2],["oncomponentevent",2],["other",2],["there",2],["retired",2],["into",2],["arrays",2],["fieldarray",2],["fieldarraysection",2],["option",2],["arrayof",2],["builder",2],["exported",2],["sources",2],["order",2],["thumb",2],["this",2],["typescript",2],["creditapplication",2],["null",2],["creditapplicationderived",2],["monthlypayment",2],["formschemanode",2],["nested",2],["field",2],["item",2],["crosssteprules",2],["checked",2],["creditapplicationmessages",2],["renders",2],["createcreditapplicationform",2],["validationmessagesprovider",2],["large",2],["answers",2],["valid",2],["through",2],["loaded",2],["formname",2],["predicates",2],["jsx",2],["property",2],["text",2],["cross",2],["derived",2],["built",2],["ones",2]]},{"id":"ui-components","file":"16-ui-components.md","title":"ui-components","purpose":"interface 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{ model: model.$.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],["reformer",20],["form",16],["componentprops",16],["value",16],["input",15],["email",15],["kit",12],["type",11],["from",11],["string",11],["import",10],["model",9],["component",8],["для",8],["placeholder",8],["tsx",8],["country",7],["const",7],["компонент",6],["без",6],["react",6],["agree",6],["useformcontrol",6],["issues",6],["disabled",6],["поля",5],["если",5],["options",5],["jsx",5],["props",5],["core",5],["один",5],["testid",5],["через",5],["onchange",5],["shouldshowissues",5],["обёртки",4],["schema",4],["как",4],["field",4],["обёртка",4],["useissuemessage",4],["formmessages",4],["message",4],["span",4],["cdk",4],["seam",4],["рендерер",4],["контрола",4],["свои",3],["driven",3],["selectasync",3],["checkboxwithlabel",3],["сам",3],["его",3],["useformbundle",3],["button",3],["registrationform",3],["function",3],["children",3],["return",3],["компоненты",3],["prop",3],["вместо",3],["onblur",3],["reformeradapter",3],["definefieldcontrol",3],["fields",3],["formcontrol",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],["createform",2],["formmodel",2],["formschemanode",2],["раз",2],["читает",2],["select",2],["etc",2],["текст",2],["замечания",2],["коду",2],["validationmessagesprovider",2],["messages",2],["всё",2],["wrong",2],["схемы",2],["advanced",2],["inputmask",2],["mask",2],["phone",2],["все",2],["aria",2],["invalid",2],["статику",2],["based",2],["myformfieldprops",2],["record",2],["text",2],["undefined",2],["setvalue",2],["target",2],["внутри",2],["выше",2],["dom",2],["статикой",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 архитектуры и прежних контрактов модели, валидации, схемы, визарда и нод):","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"15. NON-EXISTENT API (DO NOT USE)","slug":"non-existent-api-do-not-use"}],"terms":[["reformer",55],["model",53],["core",40],["form",40],["import",26],["from",25],["нет",23],["value",22],["bundle",22],["validation",20],["field",20],["email",18],["correct",16],["wrong",15],["array",15],["модели",14],["createform",14],["react",12],["schema",12],["формы",12],["node",12],["validate",11],["поля",11],["ноды",11],["component",11],["cdk",11],["kit",11],["схемы",10],["notes",10],["поле",10],["definevalidation",9],["validators",9],["узла",9],["flow",9],["type",9],["useformbundle",8],["без",8],["только",8],["правила",8],["удалено",8],["formarray",8],["required",8],["submit",8],["валидации",7],["cross",7],["validatemodel",7],["defineformbehavior",7],["behaviors",7],["массива",7],["handler",7],["схема",6],["тип",6],["useformcontrol",6],["i18n",6],["этап",6],["fieldarray",6],["items",6],["code",6],["issues",6],["сборка",6],["submission",6],["сборки",6],["типы",5],["children",5],["компонент",5],["имя",5],["item",5],["messages",5],["при",5],["strategy",5],["formgroup",5],["setvalue",5],["reset",5],["поведение",5],["узел",5],["enablewhen",5],["path",4],["validatewhen",4],["applyeach",4],["живут",4],["раннер",4],["значения",4],["удалён",4],["useissuemessage",4],["rule",4],["name",4],["compute",4],["через",4],["ключи",4],["группы",4],["json",4],["renderer",4],["message",4],["errors",4],["validationresult",4],["tree",4],["validatestep",4],["submitting",4],["isformcontrol",4],["fields",4],["watchfield",4],["computefrom",4],["city",4],["componentprops",4],["revalidatewhen",4],["input",4],["typescript",4],["api",3],["нод",3],["операторы",3],["validateasync",3],["apply",3],["useform",3],["formvalue",3],["unknown",3],["модель",3],["string",3],["instanceof",3],["проверка",3],["имена",3],["хуки",3],["useformcontrolvalue",3],["object",3],["прогон",3],["boolean",3],["group",3],["validationdefinition",3],["issue",3],["полей",3],["пути",3],["существует",3],["partial",3],["patch",3],["вложенные",3],["теперь",3],["словарь",3],["defineissuemessages",3],["params",3],["filter",3],["render",3],["дерево",3],["поток",3],["rules",3],["steps",3],["extras",3],["submitresult",3],["правил",3],["отдельный",3],["formwizard",3],["стратегия",3],["formnode",3],["fieldnode",3],["formcontrol",3],["данных",3],["ключ",3],["index",3],["key",3],["initial",3],["значение",3],["одна",3],["над",3],["formschemanode",3],["запись",3],["onchange",3],["dispose",3],["уходит",3],["testid",3],["copyfrom",3],["inputfield",3],["common",3],["const",3],["корне",2],["хука",2],["либо",2],["createmodel",2],["параметры",2],["замечания",2],["record",2],["signal",2],["сигнал",2],["i18nprovider",2],["usei18n",2],["тексты",2],["замечаний",2],["рендерер",2],["validateform",2],["validateformmodel",2],["внешний",2],["applywhen",2],["валидация",2],["строк",2],["validationschemafn",2],["удалены",2],["equalto",2],["sig",2],["фабрик",2],["правило",2],["функция",2],["fieldpath",2],["сигналами",2]]},{"id":"conditional-fields","file":"18-conditional-fields.md","title":"Условные поля — видимость, доступность и валидация","purpose":"«Показать поле X только если Y» решается тремя независимыми механизмами — выбирай по тому, что именно должно быть условным:","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, cross }) => 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-схемы). Тип схемы — `ValidationDefinition<T>`\n (`(scope: { model: FormModel<T>; cross }) => void`), фабрика-обёртка — `defineValidation<T>(fn)`.\n\n- **Cross-field — обычная функция над снимком, а не `ModelValidator (value, scope, root)`.**\n Правило значения — `Rule<T> = (value: T) => error | null`, параметров `scope` / `root` у него нет.\n Соседние поля читаются из снимка модели, а правило вешается оператором `cross(sig, check)` из\n аргумента схемы. Каноничное использование — каталог `validation/` формы `complex-multy-step-form`.\n\n ```typescript\n import { defineValidation } from '@reformer/core/validation';\n import type { ValidationIssue } from '@reformer/core';\n\n type Passwords = { password: string; confirmPassword: string };\n\n // (snapshot) => error — сравнение с соседним полем\n const passwordsMatch = (passwords: Passwords): ValidationIssue | null =>\n passwords.confirmPassword &&\n passwords.password &&\n passwords.confirmPassword !== passwords.password\n ? { code: 'confirmPassword.mismatch' } // текст кода — в словаре формы\n : null;\n\n // cross — из аргумента схемы; check получает снимок модели области:\n const passwordRules = defineValidation<Passwords>(({ model, cross }) => {\n cross(model.$.confirmPassword, passwordsMatch);\n });\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",36],["enablewhen",15],["reformer",14],["core",14],["validatewhen",13],["loantype",12],["when",11],["только",10],["schema",10],["при",10],["mortgage",10],["поля",9],["validate",9],["const",9],["cross",9],["form",9],["поле",8],["схемы",8],["формы",8],["нет",8],["import",8],["from",8],["passwords",8],["definevalidation",7],["resetondisable",7],["true",7],["null",7],["валидация",6],["compute",6],["copyfrom",6],["validatemodel",6],["validation",6],["api",6],["propertyvalue",6],["initialpayment",6],["как",6],["required",6],["модели",5],["behaviors",5],["правила",5],["оператор",5],["cond",5],["bundle",5],["скрытие",5],["узел",5],["hidewhen",5],["jsx",5],["useformcontrolvalue",5],["узла",5],["children",5],["валидации",5],["его",5],["значение",5],["typescript",5],["creditform",5],["value",5],["root",5],["confirmpassword",5],["видимость",4],["доступность",4],["остаётся",4],["значения",4],["условная",4],["удалённого",4],["defineformbehavior",4],["type",4],["boolean",4],["string",4],["или",4],["field",4],["когда",4],["правило",4],["scope",4],["быть",3],["для",3],["submission",3],["submit",3],["либо",3],["под",3],["node",3],["branch",3],["applywhen",3],["больше",3],["anti",3],["patterns",3],["контракта",3],["nonexistent",3],["условие",3],["ismortgage",3],["выше",3],["оператором",3],["validators",3],["тип",3],["чтобы",3],["return",3],["validateformmodel",3],["password",3],["условные",2],["если",2],["тому",2],["что",2],["должно",2],["внутри",2],["прогон",2],["уровнем",2],["раннером",2],["разметки",2],["вообще",2],["selector",2],["разметке",2],["условный",2],["реактивно",2],["участвует",2],["иначе",2],["сохраняется",2],["number",2],["sameemail",2],["email",2],["emailadditional",2],["export",2],["читает",2],["другого",2],["поведение",2],["createform",2],["behavior",2],["операторы",2],["cleanup",2],["сами",2],["нужен",2],["без",2],["рендерер",2],["полем",2],["условию",2],["модель",2],["снимок",2],["прогона",2],["условии",2],["ложном",2],["setissues",2],["замечания",2],["min",2],["ветка",2],["await",2],["ещё",2],["состояния",2],["раздела",2],["отправки",2],["formgroup",2],["react",2],["схема",2],["функция",2],["void",2],["validationschemafn",2],["error",2],["check",2],["аргумента",2],["validationissue",2],["passwordsmatch",2],["signatures",2],["reset",2],["reading",2],["values",2]]},{"id":"reading-values","file":"19-reading-values.md","title":"reading-values","purpose":"Значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки (`@reformer/core/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",11],["email",11],["form",7],["сигнал",6],["subscribe",6],["значения",5],["модели",5],["react",5],["typescript",5],["поля",5],["const",5],["city",5],["compute",5],["onchange",5],["сигналы",4],["значение",4],["computed",4],["группы",4],["поле",4],["peek",4],["снимок",4],["field",3],["доступ",3],["issues",3],["control",3],["только",3],["без",3],["items",3],["effect",3],["целиком",3],["модель",3],["внутри",3],["запись",3],["через",3],["нереактивный",3],["get",3],["для",3],["узел",3],["all",3],["addr",3],["amount",3],["term",3],["есть",2],["чтения",2],["хуки",2],["reformer",2],["core",2],["объект",2],["замечания",2],["shouldshowissues",2],["напрямую",2],["массива",2],["вне",2],["реактивно",2],["getvalue",2],["число",2],["под",2],["читают",2],["реактивного",2],["дерева",2],["подписка",2],["readonlysignal",2],["поэтому",2],["behaviors",2],["читаем",2],["defineformbehavior",2]]},{"id":"compute-vs-watch","file":"20-compute-vs-watch.md","title":"compute-vs-watch","purpose":"Поведение работает на сигналах модели. Писать его можно двумя способами:","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"17. COMPUTE vs ONCHANGE","slug":"compute-vs-onchange"}],"terms":[["model",44],["compute",17],["reformer",11],["core",11],["map",10],["onchange",9],["computefrom",8],["watchfield",8],["typescript",7],["только",6],["behaviors",5],["defineformbehavior",5],["для",5],["формы",5],["примитив",5],["читай",5],["value",5],["proxy",5],["без",5],["cleanup",4],["изменение",4],["async",4],["цель",4],["поле",4],["amount",4],["null",4],["import",4],["from",4],["const",4],["через",4],["array",4],["значения",4],["реакция",4],["debounce",4],["signal",4],["country",4],["dsl",3],["behavior",3],["это",3],["сигналы",3],["вне",3],["нет",3],["или",3],["target",3],["внутри",3],["поля",3],["total",3],["при",3],["пересчёт",3],["get",3],["field",3],["массива",3],["coborrowers",3],["элементов",3],["полей",3],["interestrate",3],["источников",3],["abortsignal",3],["любое",3],["модели",2],["писать",2],["его",2],["можно",2],["массив",2],["side",2],["обновление",2],["componentprops",2],["auto",2],["tracking",2],["read",2],["запись",2],["peek",2],["number",2],["myform",2],["level",2],["nested",2],["personaldata",2],["when",2],["нереактивный",2],["зависимость",2],["отследится",2],["контекста",2],["сумма",2],["реактивно",2],["reduce",2],["читает",2],["трекает",2],["length",2],["если",2],["нужен",2],["явный",2],["monthlypayment",2],["term",2],["rate",2],["помечает",2],["следующей",2],["оператор",2],["form",2],["cities",2],["city",2],["группы",2],["address",2],["addr",2],["all",2],["производное",2]]},{"id":"array-operations","file":"21-array-operations.md","title":"array-operations","purpose":"Массивы объектов — model-owned. Мутации делаются через `ModelArray` (`model.arrayField`) либо через ноду формы `FormArray` (`form.items`) — она данными не владеет и передаёт мутацию в массив модели; рендер — через ноду формы (`form.items.map` / `.at` / `.keyAt`).","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"18. ARRAY OPERATIONS","slug":"array-operations"}],"terms":[["items",34],["model",30],["form",23],["item",19],["const",10],["формы",8],["applyeach",8],["через",7],["name",7],["price",7],["массив",6],["typescript",6],["под",6],["length",6],["строки",6],["push",6],["index",6],["reformer",6],["defineformbehavior",6],["contact",6],["array",5],["ноду",5],["модели",5],["модель",5],["import",5],["from",5],["core",5],["myform",5],["row",5],["map",4],["строк",4],["move",4],["swap",4],["removeat",4],["add",4],["return",4],["div",4],["button",4],["phone",4],["address",4],["rows",4],["cross",4],["рендер",3],["keyat",3],["formgroup",3],["arrayof",3],["шаблон",3],["value",3],["массива",3],["control",3],["remove",3],["behavior",3],["enablewhen",3],["aggregateinto",3],["total",3],["объектов",2],["мутации",2],["modelarray",2],["formmodel",2],["undefined",2],["снимок",2],["число",2],["плоские",2],["значения",2],["blank",2],["индексу",2],["концом",2],["rangeerror",2],["нет",2],["fromindex",2],["toindex",2],["clear",2],["insert",2],["строка",2],["dirty",2],["форму",2],["запись",2],["fieldconfig",2],["component",2],["берутся",2],["type",2],["схемы",2],["object",2],["usearraylength",2],["formfield",2],["onclick",2],["per",2],["compute",2],["behaviors",2],["apply",2],["ручка",2],["нода",2],["phonebehavior",2],["contactbehavior",2],["phones",2],["внутри",2],["схема",2],["получает",2],["фасад",2],["modelof",2],["patch",2],["others",2],["reduce",2],["percent",2],["validation",2],["sig",2],["check",2],["скалярное",2],["поле",2],["носитель",2],["definevalidation",2],["validationissue",2],["percentagessumto100",2],["null",2],["sum",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":"Как устроено","slug":"kak-ustroeno"},{"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/model`** (сигнал → сигнал):\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",38],["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/model`) и DSL (`@reformer/core/behaviors`):\n\n```typescript\n// примитив: возвращает cleanup\nfunction syncFields<T>(a: Signal<T>, b: Signal<NoInfer<T>>, options?: { transform?: (value: T) => T }): () => void;\n\n// DSL: cleanup управляется формой\nfunction syncFields<T>(a: Signal<T>, b: Signal<NoInfer<T>>, options?: { transform?: (value: T) => T }): void;\n```\n\n`a`/`b` — сигналы (`model.$.field`) совместимого типа `T`. Тип пары задаёт `a`: поле `number | null`\nвторым аргументом к `number` не компилируется — синхронизация писала бы `null` в `number`. `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",29],["syncfields",20],["transform",9],["compute",8],["value",8],["string",8],["copyfrom",7],["typescript",7],["from",6],["when",6],["для",5],["reformer",5],["core",5],["number",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],["значения",2],["поле",2],["dsl",2],["примитив",2],["cleanup",2],["function",2],["noinfer",2],["options",2],["void",2],["типа",2],["null",2],["только",2],["нет",2],["type",2],["mirrorform",2],["export",2],["нормализация",2],["записи",2],["displayform",2],["значение",2],["stop",2],["два",2],["что",2],["обе",2],["стороны",2],["amountstring",2],["amountnumber",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` задаёт произвольное значение и **обязателен по типу**, если `null` не входит в тип поля.","keyConcepts":[],"api":"Одинаково в примитиве (`@reformer/core/model`) и DSL (`@reformer/core/behaviors`):\n\n```typescript\n// опции: поле с null — необязательны; поле без null — resetValue обязателен\ntype ResetWhenOptionsArg<T> = null extends T\n ? [options?: { resetValue?: NoInfer<T> }]\n : [options: { resetValue: NoInfer<T> }];\n\n// примитив: возвращает cleanup\nfunction resetWhen<T>(target: Signal<T>, condition: () => boolean, ...options: ResetWhenOptionsArg<T>): () => void;\n\n// DSL: cleanup управляется формой\nfunction resetWhen<T>(target: Signal<T>, condition: () => boolean, ...options: ResetWhenOptionsArg<T>): void;\n```\n\n`target` — сигнал (`model.$.field`). `condition` — реактивное условие (читает `model.*`).\n`resetValue` по умолчанию `null` — поэтому полю без `null` в типе (`string`, `number`) его нужно\nзадать явно, иначе код не компилируется. Опций `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",33],["resetwhen",22],["resetvalue",19],["condition",10],["поля",9],["null",9],["cardnumber",9],["typescript",8],["поле",7],["paymenttype",7],["card",7],["для",6],["enablewhen",5],["true",5],["reformer",5],["core",5],["defineformbehavior",5],["from",5],["resetondisable",4],["без",4],["options",4],["string",4],["number",4],["сброс",3],["behaviors",3],["type",3],["resetwhenoptionsarg",3],["примитив",3],["target",3],["field",3],["читает",3],["import",3],["const",3],["propertyvalue",3],["initialpayment",3],["patterns",3],["amount",3],["cycle",3],["detection",3],["что",3],["полей",2],["значение",2],["когда",2],["альтернатива",2],["enabled",2],["нужно",2],["способа",2],["оплаты",2],["карты",2],["умолчанию",2],["обязателен",2],["если",2],["dsl",2],["noinfer",2],["возвращает",2],["cleanup",2],["function",2],["signal",2],["boolean",2],["void",2],["иначе",2],["компилируется",2],["нет",2],["значения",2],["checkoutform",2],["export",2],["mortgageform",2],["сбрасываем",2],["как",2],["show",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/model`) и 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`) и готового набора\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",29],["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],["createform",2],["через",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/model`).\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` — `defineValidation<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 = (form: Form): ValidationIssue | null =>\n form.amount != null && form.maxAmount != null && form.amount > form.maxAmount\n ? { code: 'tooBig' }\n : null;\n\nconst schema = defineValidation<Form>(({ model, cross }) => {\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` области схемы и раннер `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",38],["revalidatewhen",21],["validatemodel",19],["amount",13],["cross",13],["maxamount",11],["reformer",11],["core",11],["void",11],["schema",10],["правило",8],["import",8],["from",8],["const",8],["form",8],["typescript",7],["defineformbehavior",7],["validation",5],["при",5],["field",5],["где",5],["формы",5],["зависит",4],["api",4],["behaviors",4],["deps",4],["amountvsmax",4],["только",4],["null",4],["перевалидация",3],["колбэк",3],["confirmpassword",3],["password",3],["propertyvalue",3],["схема",3],["схемы",3],["валидации",3],["изменении",3],["зависимости",3],["примитив",3],["revalidate",3],["definevalidation",3],["export",3],["paymentschema",3],["схеме",3],["получает",3],["снимок",3],["signatures",3],["схему",3],["поле",3],["триггера",3],["вызывает",2],["когда",2],["полей",2],["initialpayment",2],["валидация",2],["функция",2],["через",2],["поэтому",2],["cleanup",2],["function",2],["readonlysignal",2],["unknown",2],["триггеров",2],["вне",2],["смене",2],["для",2],["области",2],["loanamount",2],["passwordsmatch",2],["как",2],["так",2],["поля",2],["читает",2],["check",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-флоу — один вызов отправки сборки: `bundle.submission.submit(handler)`. Отправка — отдельный объект сборки (`FormSubmission`): она прогоняет все правила один раз, разносит замечания по нодам и зовёт обработчик со значениями модели (`model.get()`) только при статусе `valid`. Результат — объект `SubmitResult`, по которому отличают отправку (`submitted`), замечания в форме (`invalid`), сбой проверки (`error`), отмену (`cancelled`) и исключение обработчика (`failed`). После успеха — `model.reset()` (к initial-снимку) либо `model.captureInitial()` (сохранённые значения — новая точка отсчёта).","keyConcepts":[],"api":"```typescript\n// Уровень формы — сборка createForm (см. 38-form-flow.md); отправка — объект bundle.submission\nbundle.submission.submit<R>(handler: (values: T) => R | Promise<R>): Promise<SubmitResult<R>>;\ntype SubmitResult<R> =\n | { status: 'submitted'; result: R } // обработчик отработал\n | { status: 'invalid' | 'error' | 'cancelled'; validation: ValidationResult } // не вызывался\n | { status: 'failed'; error: unknown }; // обработчик бросил исключение\nbundle.submission.submitting: ReadonlySignal<boolean>; // работает обработчик\nbundle.validate(): Promise<ValidationResult>; // проверить всю форму без отправки\nbundle.validating: ReadonlySignal<boolean>; // идёт проверка\nbundle.dispose(): void; // снять сборку: поведение, подписки нод, живую проверку; модель остаётся\n\n// Отправка без сборки (визард в разметке без createForm) — без проверки\ncreateSubmission<T>({ check: undefined, values: () => T }): FormSubmission<T>;\n\n// Модель (источник истины значений):\ninterface ModelApi<T> {\n get(): T; // снимок значений — его получает обработчик submit\n set(value: T): void; // полная установка (все ключи T); при нехватке ключей — dev-предупреждение\n patch(value: DeepPartial<T>): void; // частичное слияние: вложенные группы тоже частично, массив — целиком;\n // ключ со значением undefined не трогается\n isDirty(): boolean; // отличаются ли значения от initial-снимка (цели compute не в счёт)\n reset(): void; // вернуть к initial-снимку (цели compute не пишет — их пересчитает поведение)\n captureInitial(): void; // зафиксировать текущие как новый initial\n}\n\n// Раннер без сборки — headless: находит ноды по сигналам модели, разносит по ним замечания,\n// отменяет устаревшие прогоны той же (model, schema):\nvalidateModel<T>(model: FormModel<T>, schema: ValidationDefinition<T>, options?: { touch?: boolean }): Promise<boolean>\n// true = статус 'valid': блокирующих замечаний нет (severity:'warning' не блокирует, но показывается).\n// false = 'invalid', 'error' (правило не удалось проверить — на поле замечание ruleFailed) либо 'cancelled'.\nrunValidation<T>(model, schema): Promise<ValidationResult> // только сбор\napplyValidationResult(result, options?: { touch?: boolean }): void // разнос по нодам\n\n// Нода поля/формы (точечная работа с UI-состоянием):\nform.markAsTouched(); // тронуть все поля (показать замечания)\nform.clearIssues(); // снять замечания со всех нод\nform.<field>.setIssues([{ code, params }]); // серверная/бизнес-ошибка в конкретное поле\nform.<field>.clearIssues();\nform.reset(); // к точке отсчёта модели + чистые ноды (замечания, touched, dirty)\n```","patterns":[],"antiPatterns":[{"why":"Отправка в обход проверки — правила не прогнаны, замечания не показаны","bad":"await api.send(bundle.model.get());","correctNote":"Проверка и отправка одним вызовом","correct":"await bundle.submission.submit(api.send);"},{"why":"Результат как boolean — объект всегда truthy","bad":"if (await bundle.submission.submit(api.send)) navigate('/done');","correctNote":"Статус результата","correct":"const outcome = await bundle.submission.submit(api.send);\nif (outcome.status === 'submitted') navigate('/done');"},{"why":"Методы проверки и отправки на ноде — удалены","bad":"await form.validate();\nawait form.submit(api.send);","correctNote":"Проверка — у сборки, отправка — её объект submission (см. 17-nonexistent-api.md)","correct":"await bundle.validate();\nawait bundle.submission.submit(api.send);"},{"why":"Чтение результата без await — сабмитим по неполному вердикту","bad":"const handleSubmit = (event) => {\n event.preventDefault();\n validateModel(model, schema); // Promise проигнорирован, async-правила не дождались\n send(model.get());\n};","correctNote":"bundle.submission.submit дожидается async-правил сам","correct":"const handleSubmit = async (event) => {\n event.preventDefault();\n await bundle.submission.submit(send);\n};"},{"why":"Правила в узле схемы формы — ключа validators у узла нет, схема формы правил не несёт","bad":"createForm({\n initial,\n schema: (model) => ({ children: [{ model: model.$.username, component: Input, validators: [required()] }] }),\n});","correctNote":"Валидация — отдельная схема, уходит в сборку полем validation","correct":"const rules = defineValidation<T>(({ model }) => {\n validate(model.$.username, [required()]);\n});\ncreateForm({ initial, schema, validation: rules });"},{"why":"model.reset() до ответа сервера — потеря данных при ошибке","bad":"await api.send(model.get());\nmodel.reset();","correctNote":"Reset только после успеха — внутри обработчика либо по статусу 'submitted'","correct":"const outcome = await bundle.submission.submit(async (values) => {\n await api.send(values);\n bundle.model.reset();\n});"},{"why":"Сборка в тесте или на сервере брошена — поведение и живая проверка продолжают работать","bad":"const bundle = createForm<RegistrationFormData>({ initial, schema, validation: rules });\nawait bundle.submission.submit(register);","correctNote":"Владелец сборки снимает её сам; модель при этом остаётся","correct":"bundle.dispose();"},{"why":"Сброс полей по очереди — многословно","bad":"model.username = '';\nmodel.email = '';\nmodel.password = '';","correctNote":"model.reset() — к initial-снимку одним вызовом","correct":"model.reset();"}],"examples":[],"troubleshooting":[],"seeAlso":["[38-form-flow.md](./38-form-flow.md) — уровень формы: `validate`, `validateStep`, `submission`, поток шагов","[29-async-preload.md](./29-async-preload.md) — initial values и preload через `set`/`patch`","[13-multi-step.md](./13-multi-step.md) — пошаговая форма: поток `flow`, визард","[27-revalidate-when.md](./27-revalidate-when.md) — мост «поведение → валидация»: `revalidateWhen([deps], () => void validateModel(model, schema))`","[36-issue-messages.md](./36-issue-messages.md) — тексты замечаний по коду","[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",51],["submit",27],["reset",27],["bundle",27],["form",25],["const",24],["await",23],["submission",19],["замечания",17],["initial",15],["без",15],["async",15],["outcome",15],["правила",14],["обработчик",13],["api",13],["validate",13],["после",12],["validation",12],["typescript",12],["values",12],["status",12],["поля",12],["username",12],["формы",11],["сборки",11],["отправка",11],["значения",11],["поле",11],["форма",11],["send",11],["модели",10],["schema",10],["поведение",10],["отправки",9],["при",9],["error",9],["либо",9],["createform",9],["required",9],["email",9],["registration",9],["get",8],["только",8],["promise",8],["форму",8],["проверка",8],["модель",8],["password",8],["event",8],["button",8],["submitted",7],["проверки",7],["cancelled",7],["reformer",7],["core",7],["type",7],["result",7],["boolean",7],["dispose",7],["void",7],["return",7],["сброс",7],["invalid",6],["исключение",6],["ноды",6],["замечаний",6],["code",6],["import",6],["from",6],["registrationformdata",6],["register",6],["объект",5],["результат",5],["отправку",5],["отсчёта",5],["валидация",5],["сборку",5],["flow",5],["нод",5],["проверку",5],["patch",5],["compute",5],["true",5],["нет",5],["правило",5],["замечание",5],["правил",5],["function",5],["handlesubmit",5],["case",5],["break",5],["данные",5],["сам",5],["снимает",5],["failed",4],["снимку",4],["captureinitial",4],["схема",4],["definevalidation",4],["раннер",4],["validatemodel",4],["уровень",4],["проверить",4],["живую",4],["значений",4],["set",4],["isdirty",4],["как",4],["clearissues",4],["полей",4],["прогон",4],["react",4],["string",4],["response",4],["preventdefault",4],["сервера",4],["enablewhen",4],["или",4],["чтобы",4],["resetondisable",4],["один",3],["вызов",3],["handler",3],["все",3],["valid",3],["submitresult",3],["уходит",3],["сборка",3],["validationresult",3],["validating",3],["подписки",3],["снимок",3],["получает",3],["целиком",3],["цели",3],["formmodel",3],["статус",3],["severity",3],["warning",3],["блокирует",3],["false",3],["удалось",3],["runvalidation",3],["field",3],["ошибка",3],["touched",3],["полями",3],["тронутой",3],["трогает",3],["tsx",3],["useformbundle",3],["minlength",3],["validators",3],["createregistration",3],["formfield",3],["control",3],["testid",3],["результата",3],["navigate",3],["уже",3],["пока",3],["ещё",3],["запись",3],["issue",3],["messages",3],["очистить",3],["createmodel",3],["данных",3],["вызовом",3],["rules",3],["момент",3],["записей",3],["поведения",3],["загруженные",3],["входят",3],["when",3],["preload",3],["отдельный",2],["formsubmission",2],["она",2],["раз",2],["разносит",2],["нодам",2],["форме",2],["сбой",2],["обработчика",2],["успеха",2],["точка",2],["через",2],["полем",2],["тот",2],["бросил",2],["submitting",2],["readonlysignal",2],["работает",2],["снять",2],["остаётся",2],["визард",2],["undefined",2]]},{"id":"async-preload","file":"29-async-preload.md","title":"Async Preload — Загрузка начальных значений и справочников","purpose":"Загрузка формы данными с сервера: (1) **initial values** в `createModel(...)` / `createForm({ initial })`; (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.\n // DTO без части ключей: поля остаются прежними, в dev — предупреждение.\nmodel.patch(value: DeepPartial<T>): void; // частичное слияние (только переданные ключи, на любой глубине групп; undefined — «не трогать»). Не меняет 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 bundle = createForm<T>({ initial, schema }); // КАЖДЫЙ рендер!\n}","correctNote":"Стабильная сборка: useFormBundle вызывает фабрику ровно один раз (useMemo React вправе","correct":"// сбросить и пересобрать форму, потеряв введённое)\nfunction MyForm() {\n const { model, form } = useFormBundle(() => createForm<T>({ initial, schema }));\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",31],["set",16],["form",15],["initial",12],["updatecomponentprops",11],["после",10],["const",9],["async",8],["через",8],["options",8],["reset",8],["error",8],["return",8],["cancelled",8],["preload",7],["queuemicrotask",7],["для",6],["typescript",6],["isloading",6],["null",6],["applicationid",6],["region",6],["createmodel",5],["patch",5],["componentprops",5],["void",5],["import",5],["from",5],["reformer",5],["username",5],["true",5],["schema",5],["label",5],["useeffect",5],["setstate",5],["false",5],["onchange",5],["загрузка",4],["значений",4],["чтобы",4],["value",4],["string",4],["core",4],["language",4],["marketing",4],["await",4],["data",4],["city",4],["каждый",4],["сервера",3],["values",3],["createform",3],["react",3],["race",3],["guard",3],["динамические",3],["опции",3],["поля",3],["captureinitial",3],["возвращает",3],["вызови",3],["component",3],["loadingstate",3],["function",3],["appresp",3],["dictsresp",3],["cities",3],["err",3],["useformbundle",3],["справочников",2],["загрузки",2],["external",2],["hook",2],["field",2],["реактивными",2],["эффектами",2],["модель",2],["load",2],["dto",2],["меняет",2],["без",2],["снимку",2],["данные",2],["отсчёта",2],["createformfrommodel",2],["type",2],["formschemanode",2],["input",2],["selectasync",2],["checkboxwithlabel",2],["interface",2],["profileform",2],["boolean",2],["usestate",2],["formmodel",2],["formgroup",2],["export",2],["creditapplicationform",2],["state",2],["let",2],["try",2],["status",2],["catch",2],["сборка",2],["при",2],["defineformbehavior",2],["signal",2],["рендер",2],["раз",2],["myform",2],["bundle",2],["сбросить",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`-кастов.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[],"examples":[],"troubleshooting":[],"seeAlso":[],"sections":[{"heading":"30. TYPE-SAFETY RECIPES","slug":"type-safety-recipes"}],"terms":[["model",30],["reformer",21],["core",21],["null",14],["type",13],["recipe",11],["typescript",10],["import",10],["from",10],["string",10],["const",10],["form",9],["interface",9],["formgroup",7],["validation",6],["compute",6],["extends",6],["formcontrol",6],["loanamount",6],["для",6],["value",6],["outcome",6],["модель",5],["behaviors",5],["useformcontrol",5],["validate",5],["rule",5],["поля",5],["export",5],["number",5],["creditapplicationform",5],["field",5],["под",5],["personaldata",5],["cross",5],["application",5],["status",5],["без",4],["definevalidation",4],["min",4],["record",4],["formarray",4],["object",4],["массив",4],["address",4],["return",4],["поле",4],["control",4],["bundle",4],["форма",3],["react",3],["схема",3],["defineformbehavior",3],["createmodel",3],["createform",3],["formmodel",3],["formschemanode",3],["примитив",3],["нет",3],["вид",3],["ноды",3],["date",3],["propertyitem",3],["properties",3],["unknown",3],["function",3],["snapshot",3],["модели",3],["узла",3],["тип",3],["форм",3],["item",3],["правила",3],["validationissue",3],["initialpayment",3],["формы",3],["напрямую",3],["полей",3],["minage",3],["birthdate",3],["api",3],["error",3],["transition",3],["типы",2],["над",2],["валидации",2],["операторы",2],["validatemodel",2],["validators",2],["defineissuemessages",2],["required",2],["max",2],["onchange",2],["оба",2],["работают",2],["только",2],["значения",2],["index",2],["signature",2],["через",2],["creditapplication",2],["tags",2],["типом",2],["creditapplicationderived",2],["monthlypayment",2],["своих",2],["getvalue",2],["привязка",2],["подформа",2],["сверяется",2],["правил",2],["схеме",2],["component",2],["билдер",2],["получает",2],["loan_amount_rules",2],["валидаторы",2],["схемы",2],["initialpaymentvsproperty",2],["propertyvalue",2],["code",2],["читает",2],["касты",2],["minlength",2],["используй",2],["myfieldprops",2],["issues",2],["гарды",2],["isformcontrol",2],["isformgroup",2],["isformarray",2],["именованные",2],["validateadultage",2],["age",2],["new",2],["getfullyear",2],["внутри",2],["submission",2],["submit",2],["flow",2],["next",2],["await",2],["send",2],["else",2],["invalid",2]]},{"id":"async-validator-debounce","file":"31-async-validator-debounce.md","title":"async-validator-debounce","purpose":"Для проверок типа «уникальность email», «валидация ИНН через API», «проверка адреса» — async-правило это `AsyncRule<T>` = `(value, { signal }) => Promise<ValidationIssue | null>`. Оно регистрируется оператором `validateAsync(sig, [asyncRules])` внутри схемы валидации (`@reformer/core/validation`) и исполняется прогоном — уровнем формы (`bundle.validate()`, `bundle.submission.submit(handler)`, `flow.next()`) либо раннером `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",25],["email",20],["model",20],["const",18],["правила",13],["await",13],["signal",12],["прогон",12],["value",11],["null",10],["validateasync",10],["bundle",9],["validate",9],["validatemodel",9],["правило",8],["reformer",8],["core",8],["validation",8],["return",8],["sync",8],["schema",8],["debounce",8],["api",7],["формы",7],["pending",7],["error",6],["import",6],["from",6],["string",6],["res",6],["поля",6],["через",5],["asyncrule",5],["promise",5],["либо",5],["поле",5],["required",5],["его",5],["fetch",5],["только",5],["сборки",5],["пока",5],["submission",4],["submit",4],["createform",4],["json",4],["definevalidation",4],["сбой",4],["code",4],["rulefailed",4],["удалось",4],["проверить",4],["abortsignal",4],["прогона",4],["раннер",4],["outcome",4],["valid",4],["замечания",4],["runvalidation",4],["result",4],["onchange",4],["username",4],["для",3],["это",3],["sig",3],["если",3],["статус",3],["нужен",3],["check",3],["available",3],["boolean",3],["signup",3],["при",3],["cross",3],["form",3],["результат",3],["status",3],["applyvalidationresult",3],["get",3],["отмена",3],["preload",3],["валидация",2],["проверка",2],["validationissue",2],["оператором",2],["asyncrules",2],["схемы",2],["исполняется",2],["прогоном",2],["уровнем",2],["параллельно",2],["all",2],["дожидается",2],["схема",2],["блокирует",2],["получает",2],["замечание",2],["повторите",2],["попытку",2],["форму",2],["createmodel",2],["устаревшего",2],["flight",2],["checkemailunique",2],["try",2],["catch",2],["сам",2],["encodeuricomponent",2],["разными",2],["операторами",2],["одном",2],["сигнале",2],["обработчик",2],["без",2],["ambient",2],["прогоняет",2],["схему",2],["полей",2],["useformcontrol",2],["даёт",2],["true",2],["cancelled",2],["нужно",2],["полный",2],["отправки",2],["send",2],["сбор",2],["инлайн",2],["держи",2],["формат",2],["inn",2],["индикатор",2],["свой",2],["issues",2],["shouldshowissues",2],["message",2],["revalidatewhen",2],["живой",2],["strategy",2],["defineformbehavior",2],["void",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`): переиспользует раннер схемы как ЕДИНСТВЕННЫЙ движок (разнос ошибок по нодам, отмена устаревших прогонов, дедуп по `(model, schema)`), второго движка не заводит. Других триггеров валидации нет: у ноды поля собственных валидаторов, `updateOn` и `debounce` не бывает — когда запускать проверку, решает только стратегия.","keyConcepts":[],"api":"Три точки входа к одному контроллеру:\n\n- **сборка `createForm`** — `validation: { rules?, extras?, strategy, debounce, liveAfterSubmit }`.\n Контроллер собирает уровень формы, армирует `useFormBundle` (в эффекте, не при SSR);\n `bundle.validate()` и `bundle.submission.submit(handler)` — полный прогон с раскрытием замечаний.\n Это путь по умолчанию для формы со сборкой;\n- **React-хук `useFormValidation`** из `@reformer/core/react` — для формы без сборки\n (`createFormFromModel` + своя схема);\n- **headless-фабрика `createFormValidation`** из `@reformer/core/validation` — для SSR / тестов /\n не-React потребителей.\n\n```typescript\n// Сборка (@reformer/core)\ncreateForm<T>({\n …,\n validation: {\n rules?: ValidationDefinition<T>; // полный набор; без него — правила шагов flow + extras\n extras?: ValidationDefinition<T>;\n strategy?: ValidationStrategyKind; // default 'submit'\n debounce?: number;\n liveAfterSubmit?: 'change' | 'blur';\n },\n});\n\n// React (@reformer/core/react)\nfunction useFormValidation<T>(args: {\n model: FormModel<T>;\n schema: ValidationDefinition<T>; // СТАБИЛЬНАЯ ссылка (module-level const / useMemo)\n strategy?: ValidationStrategyKind; // default 'submit'\n debounce?: number; // мс, для live-фаз (change / live-часть afterFirstSubmit)\n liveAfterSubmit?: 'change' | 'blur'; // default 'change'\n}): {\n submit: () => Promise<boolean>; // попытка отправки: при отказе раскрыта вся форма, afterFirstSubmit → живая фаза\n validate: () => Promise<boolean>; // проверка без отправки: раскрыты проверенные поля, фаза не меняется\n isValidating: boolean;\n};\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 FormValidationRunOptions {\n touch?: true | 'all'; // true (default) — проверенные поля; 'all' — при отказе ещё и вся форма\n submit?: boolean; // прогон — попытка отправки: afterFirstSubmit → живая фаза\n}\ninterface FormValidationController {\n validate(): Promise<boolean>; // нейтральная проверка: touch:true, фазу afterFirstSubmit не трогает; true — только valid\n run(options?: FormValidationRunOptions): Promise<ValidationResult>; // valid | invalid | error | cancelled\n start(): () => void; // армит реактивные подписки (ТОЛЬКО на клиенте) → dispose\n dispose(): void; // снять подписки и таймеры, отменить идущий прогон (он получит cancelled)\n readonly isValidating: boolean;\n readonly validating: ReadonlySignal<boolean>;\n}\nfunction createFormValidation<T>(\n model: FormModel<T>,\n schema: ValidationDefinition<T>,\n options?: ValidationStrategyOptions,\n): FormValidationController;\n```\n\n- `useFormValidation` мемоизирует контроллер по\n `[model, schema, strategy, debounce, liveAfterSubmit]` и армит стратегию в `useEffect`\n (SSR-safe). `submit()` = `controller.run({ touch: 'all', submit: true })`, `validate()` =\n `controller.validate()`; обе — стабильные ссылки, отвечают `true` только при `valid`.\n- `createFormValidation` ЧИСТА до `start()` (никаких подписок) → SSR-safe; `start()` возвращает\n `dispose` и арминует триггеры только на клиенте.","patterns":[],"antiPatterns":[{"why":"schema пересоздаётся каждый рендер → контроллер и дедуп раннера ломаются","bad":"function MyForm() {\n const schema = defineValidation<Form>(({ model }) => { … }); // новая ссылка на каждый рендер\n useFormValidation({ model, schema, strategy: 'change' });\n}","correctNote":"Стабильная ссылка: module-level const (или useMemo с пустыми deps)","correct":"const formValidation = defineValidation<Form>(({ model }) => { … });\nfunction MyForm() {\n useFormValidation({ model, schema: formValidation, strategy: 'change' });\n}"},{"why":"updateOn / debounce / validators в узле схемы — таких ключей у узла нет, сборка их не читает","bad":"// { model: model.$.email, component: Input, updateOn: 'blur', validators: [required()] }","correctNote":"Момент запуска — стратегия формы; правила — схема валидации","correct":"// createForm({ …, validation: { rules: formValidation, strategy: 'blur', debounce: 300 } })"},{"why":"Своя стратегия поверх сборки: второй контроллер на ту же модель и правила","bad":"const bundle = useFormBundle(() => createForm({ …, validation: rules }));\nuseFormValidation({ model: bundle.model, schema: rules, strategy: 'change' });","correctNote":"Стратегия — полем validation сборки; армирует useFormBundle","correct":"const bundle = useFormBundle(() => createForm({ …, validation: { rules, strategy: 'change' } }));"},{"why":"start() на сервере — арминует реактивные подписки при SSR","bad":"const controller = createFormValidation(model, schema, { strategy: 'change' });\ncontroller.start(); // на сервере подписок быть не должно","correctNote":"Фабрика чиста до start(); армить только на клиенте (useFormValidation делает это в useEffect)","correct":"const controller = createFormValidation(model, schema, { strategy: 'change' });\nif (typeof window !== 'undefined') controller.start();"},{"why":"afterFirstSubmit: ждать живой фазы после проверки — validate() фазу не переключает","bad":"await bundle.validate(); // форма в тихой фазе, ввод не перепроверяется","correctNote":"Живую фазу включает попытка отправки","correct":"await bundle.submission.submit(api.save); // без сборки — submit() хука useFormValidation"}],"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) — многошаговая форма: поток `flow`, `bundle.validateStep(id)`","[38-form-flow.md](./38-form-flow.md) — уровень формы: `validate`, `submission`, поток шагов"],"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":[["submit",29],["model",26],["validate",24],["const",23],["strategy",18],["validation",15],["debounce",15],["change",15],["schema",14],["bundle",14],["useformvalidation",13],["reformer",13],["core",13],["только",13],["afterfirstsubmit",13],["controller",12],["form",12],["при",11],["react",11],["flow",10],["форма",10],["true",10],["фазу",10],["rules",9],["blur",9],["отправки",9],["стратегия",8],["createform",8],["useformbundle",8],["submission",8],["createformvalidation",8],["start",8],["import",8],["from",8],["email",8],["поля",7],["формы",7],["typescript",7],["function",7],["boolean",7],["живая",7],["фаза",7],["await",7],["правила",6],["extras",6],["liveaftersubmit",6],["ssr",6],["без",6],["сборки",6],["default",6],["попытка",6],["вся",6],["проверка",6],["touch",6],["живую",6],["замечания",6],["нет",5],["definevalidation",5],["api",5],["usememo",5],["live",5],["отказе",5],["all",5],["dispose",5],["шага",5],["username",5],["password",5],["onsubmit",5],["async",5],["required",5],["button",5],["валидации",4],["сборка",4],["контроллер",4],["прогон",4],["для",4],["validationdefinition",4],["validationstrategykind",4],["promise",4],["isvalidating",4],["type",4],["run",4],["void",4],["клиенте",4],["форму",4],["каждый",4],["registrationdata",4],["registrationvalidation",4],["event",4],["все",4],["save",4],["credit",4],["когда",3],["ошибок",3],["дедуп",3],["updateon",3],["армирует",3],["полный",3],["замечаний",3],["createformfrommodel",3],["схема",3],["headless",3],["шагов",3],["number",3],["ссылка",3],["module",3],["level",3],["проверенные",3],["interface",3],["valid",3],["cancelled",3],["подписки",3],["useeffect",3],["включает",3],["хука",3],["validatestep",3],["next",3],["проверить",3],["тихой",3],["фазе",3],["отправка",3],["false",3],["touched",3],["ввод",3],["тихо",3],["переключает",3],["дальше",3],["полем",3],["tsx",3],["registration",3],["раскрывает",3],["полей",3],["minlength",3],["validators",3],["string",3],["formfield",3],["control",3],["поток",3],["steps",3],["step",3],["formvalidation",3],["стратегии",2],["ручной",2],["markastouched",2],["validatemodel",2],["схемы",2],["движок",2],["отмена",2],["устаревших",2],["прогонов",2],["проверку",2],["момент",2],["запуска",2],["раскрытие",2],["уровень",2],["эффекте",2],["handler",2],["это",2],["своя",2],["фабрика",2],["набор",2],["formmodel",2],["стабильная",2],["validationstrategyoptions",2],["formvalidationrunoptions",2],["ещё",2],["formvalidationcontroller",2],["options",2],["армит",2],["реактивные",2],["readonly",2],["стратегию",2],["safe",2],["чиста",2],["подписок",2],["арминует",2],["триггеры",2],["кнопка",2],["остаётся",2],["всё",2],["фокуса",2],["dirty",2],["первой",2],["отправке",2],["далее",2],["createregistration",2],["registrationform",2],["formevent",2],["preventdefault",2],["переводит",2],["проверенных",2],["createmodel",2],["раннера",2],["return",2],["она",2],["делает",2],["прогоняет",2],["шаги",2],["createcredit",2],["правил",2]]},{"id":"i18n","file":"34-i18n.md","title":"i18n — локаль, загрузка словарей и провайдер","purpose":"Подпуть `@reformer/core/i18n` — общий рантайм локализации для всех пакетов `@reformer/*` без React: объект локали, загрузка словарей, перевод и форматирование, тексты замечаний валидации. React-часть — провайдер `I18nProvider` и хуки `useI18n`, `useMessages`, `useIssueMessage` — отдаёт `@reformer/core/react`. Язык задаётся провайдером, подгружается динамически и переключается на лету; узлы формы при этом не пересоздаются.","keyConcepts":[],"api":"","patterns":[],"antiPatterns":[{"why":"Импорт из главной бочки — React-части локализации там нет","bad":"import { I18nProvider } from '@reformer/core';","correctNote":"Провайдер и хуки — из @reformer/core/react, локаль и перевод — из @reformer/core/i18n"},{"why":"Новый загрузчик на каждый рендер: кэш теряется, локаль запрашивается заново на каждый рендер","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":"Текст в правиле: у RuleOptions и ValidationIssue нет message — не скомпилируется","bad":"required({ message: 'Укажите телефон' });","correctNote":"Свой код в правиле, текст — в словаре формы (константа модуля), на экран — через резолвер","correct":"required({ code: 'phone.required' });\nconst phoneMessages = defineIssueMessages({ 'phone.required': 'Укажите телефон' }); // в модуле\nconst message = useIssueMessage(phoneMessages); // в компоненте\n<span>{message(issues[0])}</span>;"},{"why":"Настройки локали без словаря: объект без поля messages принимается за словарь сообщений","bad":"const source = () => ({ weekStartsOn: 1 });","correctNote":"Часть локали узнаётся по полю messages — отдавайте его хотя бы пустым","correct":"const source = () => ({ weekStartsOn: 1, messages: {} });"}],"examples":[],"troubleshooting":[],"seeAlso":["[36-issue-messages.md](./36-issue-messages.md) — тексты замечаний: коды, словарь формы, резолвер","[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-zamechaniy-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",29],["core",20],["code",19],["локаль",15],["lang",14],["локали",13],["messages",13],["const",13],["react",12],["формы",12],["словарь",12],["locale",12],["i18n",10],["usei18n",10],["для",9],["нет",9],["языка",9],["без",8],["i18nprovider",8],["при",8],["typescript",8],["date",8],["number",8],["import",8],["from",8],["cdk",8],["load",8],["loadlocale",7],["validation",7],["message",7],["провайдер",6],["объект",6],["валидации",6],["json",6],["loadcorelocale",6],["приложения",6],["error",6],["текст",6],["код",6],["required",6],["usemessages",5],["useissuemessage",5],["провайдера",5],["string",5],["plural",5],["ключ",5],["источник",5],["свой",5],["kit",5],["tsx",5],["словаря",5],["словаре",5],["formmessages",5],["issue",5],["загрузка",4],["тексты",4],["замечаний",4],["язык",4],["пакета",4],["сообщение",4],["weekstartson",4],["поэтому",4],["файла",4],["other",4],["числа",4],["строкой",4],["createlocaleloader",4],["ядра",4],["или",4],["null",4],["что",4],["ядро",4],["сам",4],["cart",4],["params",4],["резолвер",4],["defineissuemessages",4],["minlength",4],["span",4],["total",4],["словарей",3],["локализации",3],["пакетов",3],["хуки",3],["отдаёт",3],["импорт",3],["одном",3],["bcp",3],["icu",3],["ключи",3],["поля",3],["сообщений",3],["подстановка",3],["файл",3],["count",3],["файлов",3],["встроенные",3],["целиком",3],["запрос",3],["отказ",3],["загрузки",3],["source",3],["уже",3],["preload",3],["загрузчик",3],["ssr",3],["filesize",3],["только",3],["строки",3],["function",3],["return",3],["константа",3],["модуля",3],["fallback",3],["пока",3],["рендер",3],["либо",3],["английский",3],["email",3],["вне",3],["встроенный",3],["control",3],["issues",3],["его",3],["console",3],["описатели",3],["msg",3],["перевод",2],["часть",2],["переключается",2],["работает",2],["английском",2],["английская",2],["главной",2],["бочки",2],["intl",2],["необязательно",2],["fns",2],["можно",2],["сервер",2],["сети",2],["profile",2],["title",2],["extendlocale",2],["patch",2],["словари",2],["сливаются",2],["name",2],["one",2],["few",2],["many",2],["значение",2],["select",2],["внутри",2],["число",2],["языку",2],["даты",2],["отдельно",2],["передавайте",2],["готовой",2],["приложение",2],["функция",2],["fetchmessages",2],["export",2],["чанк",2],["ничего",2],["коду",2],["undefined",2],["загрузчика",2],["трёх",2],["видах",2],["синхронный",2],["тесты",2],["пакеты",2],["один",2],["ошибки",2],["плюс",2],["setlang",2],["value",2],["ссылка",2],["самая",2],["первая",2],["onerror",2],["готовый",2],["вместо",2],["экране",2],["новый",2],["pending",2],["ошибка",2],["retry",2],["запрашивается",2],["тем",2],["показывается",2],["там",2],["заменяет",2],["поддерева",2],["empty",2],["ключа",2],["выводится",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",90],["schema",21],["массив",15],["без",15],["const",15],["node",15],["component",14],["узел",13],["apply",13],["схема",12],["узла",12],["модели",12],["address",12],["flow",12],["формы",11],["applyeach",11],["под",11],["поведение",10],["rules",10],["значения",10],["модель",10],["его",10],["form",10],["только",9],["схемы",9],["поля",9],["cross",9],["part",9],["массива",9],["нет",9],["createform",8],["items",8],["validate",8],["arrayof",8],["шаблон",8],["children",8],["правила",8],["credit",8],["submission",8],["сборка",7],["item",7],["правило",7],["reformer",7],["core",7],["city",7],["один",7],["selector",7],["required",7],["одна",6],["одно",6],["дерево",6],["поле",6],["loantype",6],["import",6],["from",6],["creditform",6],["street",6],["componentprops",6],["области",6],["submit",6],["hidewhen",6],["шагов",6],["для",5],["рендерер",5],["json",5],["или",5],["value",5],["строки",5],["аргумента",5],["форм",5],["подформа",5],["push",5],["как",5],["typescript",5],["coborrower",5],["registrationaddress",5],["formschemanode",5],["через",5],["rule",5],["loan",5],["сборки",5],["поток",5],["поведения",5],["jsx",4],["привязка",4],["ручка",4],["defineformbehavior",4],["тип",4],["group",4],["область",4],["createmodel",4],["export",4],["coborrowers",4],["элемента",4],["значение",4],["внутри",4],["шаблона",4],["получают",4],["formmodel",4],["label",4],["группа",4],["подформы",4],["definevalidation",4],["validation",4],["null",4],["code",4],["либо",4],["result",4],["cancelled",4],["пока",4],["отправка",4],["ismortgage",4],["при",4],["steps",4],["форма",3],["узлов",3],["билдер",3],["документ",3],["результат",3],["array",3],["читает",3],["оператор",3],["mortgage",3],["number",3],["name",3],["нового",3],["бросает",3],["раз",3],["box",3],["input",3],["функция",3],["dev",3],["ноды",3],["данных",3],["addressrules",3],["снимок",3],["validatestep",3],["error",3],["async",3],["проверка",3],["уровень",3],["oncomponentevent",3],["onmount",3],["rendereffect",3],["части",3],["bundle",3],["проверки",3],["values",3],["монтирования",3],["сборку",3],["одной",3],["исключение",3],["useformbundle",3],["initial",3],["registry",3],["сам",3],["разметка",2],["кто",2],["ключи",2],["имя",2],["удалены",2],["закрыт",2],["each",2],["рендера",2],["валидация",2],["itembehavior",2],["начальные",2],["blankphone",2],["blankcoborrower",2],["phones",2],["createcreditmodel",2],["tags",2],["нужен",2],["blank",2],["возвращает",2],["после",2],["загрузка",2],["ошибку",2],["подсказкой",2],["structuredclone",2],["получает",2],["creditschema",2],["лист",2],["email",2],["узлы",2],["поддерева",2],["принадлежат",2],["той",2],["вызывается",2],["row",2],["validatewhen",2],["coborrowerrules",2],["contactsrules",2],["residenceaddress",2],["остаётся",2],["validationissue",2],["типом",2],["замечания",2],["defineissuemessages",2],["таким",2]]},{"id":"issue-messages","file":"36-issue-messages.md","title":"Тексты замечаний валидации — коды, словарь формы и резолвер","purpose":"Замечание валидации текста не несёт. Правило сообщает **что** нарушено (`code`) и **с какими числами** (`params`), а отображаемую строку по коду собирает резолвер в момент показа:","keyConcepts":[],"api":"```typescript\n// @reformer/core — опции фабрик @reformer/core/validators (второй или последний аргумент)\ninterface RuleOptions {\n code?: string; // свой код вместо встроенного; принято `<поле>.<правило>`\n params?: Readonly<Record<string, unknown>>; // доп. параметры — поверх параметров правила\n}\n\n// @reformer/core/i18n — без React\ntype IssueMessage =\n | string // шаблон: 'Не меньше {minLength} символов', plural, select\n | ((params: Readonly<Record<string, unknown>>, i18n: I18nHandle) => string);\ntype IssueMessages = Readonly<Record<string, IssueMessage>>; // код → текст\n\nfunction defineIssueMessages<const M extends IssueMessages>(messages: M): M; // возвращает аргумент\nfunction resolveIssueMessage(\n issue: ValidationIssue,\n i18n: I18nHandle, // createI18n(locale)\n messages?: IssueMessages // словарь формы\n): string;\n\n// @reformer/core/react\nfunction useIssueMessage(messages?: IssueMessages): (issue: ValidationIssue) => string;\n\n// @reformer/cdk — словарь формы для поддерева; FormField читает его сам\nfunction ValidationMessagesProvider(props: {\n messages?: IssueMessages;\n resolver?: (issue: ValidationIssue) => string; // полное переопределение\n children: ReactNode;\n}): ReactNode;\nfunction useIssueMessageResolver(): (issue: ValidationIssue) => string;\n```","patterns":[],"antiPatterns":[{"why":"Текст в правиле: у RuleOptions нет message — не скомпилируется","bad":"required({ message: 'Укажите телефон' });","correctNote":"Свой код в правиле, текст — в словаре формы","correct":"required({ code: 'phone.required' });\nconst messages = defineIssueMessages({ 'phone.required': 'Укажите телефон' });"},{"why":"Словарь, собранный в рендере: новая ссылка — резолвер пересоздаётся на каждый рендер","bad":"const message = useIssueMessage(defineIssueMessages({ 'phone.required': 'Укажите телефон' }));","correctNote":"Словарь — константа модуля","correct":"const messages = defineIssueMessages({ 'phone.required': 'Укажите телефон' });\nfunction PhoneIssues() {\n const message = useIssueMessage(messages);\n /* … */\n}"},{"why":"Показ замечания как строки: текста у замечания нет","bad":"<span>{issues[0].message}</span>","correctNote":"Текст — через резолвер","correct":"<span>{message(issues[0])}</span>"},{"why":"Свой код без записи в словаре — на экране сам код «loanAmount.required»","bad":"validate(model.$.loanAmount, [required({ code: 'loanAmount.required' })]);","correctNote":"Каждому своему коду — запись в словаре формы (или validation.<код> в локали приложения)","correct":"defineIssueMessages({ 'loanAmount.required': 'Укажите сумму кредита' });"},{"why":"Число с разрядами шаблоном — шаблон печатает 50000 без пробелов","bad":"'loanAmount.min': 'Минимум {min} ₽',","correctNote":"Функция форматирует число сама","correct":"'loanAmount.min': (params, i18n) => `Минимум ${i18n.number(Number(params.min))} ₽`,"}],"examples":[],"troubleshooting":[],"seeAlso":["[34-i18n.md](./34-i18n.md) — локаль, загрузка словарей, `I18nProvider`","[03-api-signatures.md](./03-api-signatures.md) — фабрики правил и `RuleOptions`","[38-form-flow.md](./38-form-flow.md) — `bundle.submission.submit` и проверка шагов","[17-nonexistent-api.md](./17-nonexistent-api.md) — удалённые `message`, `messageKey`, `ValidationError`"],"sections":[{"heading":"Purpose","slug":"purpose-12"},{"heading":"API","slug":"api-9"},{"heading":"Examples","slug":"examples-9"},{"heading":"Anti-patterns","slug":"anti-patterns-10"},{"heading":"See also","slug":"see-also-12"}],"terms":[["required",28],["код",23],["i18n",23],["code",21],["reformer",17],["формы",16],["loanamount",16],["params",14],["string",14],["minlength",13],["свой",13],["messages",13],["словаре",13],["текст",12],["core",12],["const",12],["min",12],["message",11],["словарь",10],["typescript",10],["validation",10],["import",10],["from",10],["defineissuemessages",9],["phone",9],["max",9],["validationissue",7],["readonly",7],["правила",7],["api",7],["без",7],["function",7],["issue",7],["локали",7],["loan",7],["creditmessages",7],["укажите",7],["резолвер",6],["нет",6],["поля",6],["useissuemessage",6],["validate",6],["model",6],["правило",5],["встроенный",5],["замечания",5],["параметры",5],["react",5],["шаблон",5],["issuemessages",5],["cdk",5],["validationmessagesprovider",5],["приложения",5],["email",5],["number",5],["null",5],["record",4],["nonexistent",4],["resolveissuemessage",4],["сам",4],["запись",4],["словаря",4],["minage",4],["maxfilesize",4],["minfilesize",4],["maxtotalfilesize",4],["функция",4],["локаль",4],["минимум",4],["tsx",4],["form",4],["issues",4],["телефон",4],["span",4],["тексты",3],["замечаний",3],["коды",3],["текста",3],["unknown",3],["или",3],["ruleoptions",3],["type",3],["символов",3],["plural",3],["createi18n",3],["для",3],["formfield",3],["поиска",3],["maxlength",3],["pattern",3],["multipleof",3],["maxfiles",3],["minfiles",3],["date",3],["число",3],["export",3],["loanpurpose",3],["caryear",3],["сумму",3],["кредита",3],["control",3],["shouldshowissues",3],["сервера",3],["propertyvalue",3],["валидации",2],["замечание",2],["что",2],["числами",2],["строку",2],["коду",2],["interface",2],["либо",2],["warning",2],["получает",2],["кода",2],["опций",2],["messagekey",2],["правил",2],["validators",2],["аргумент",2],["встроенного",2],["поле",2],["поверх",2],["issuemessage",2],["меньше",2],["select",2],["i18nhandle",2],["locale",2],["поддерева",2],["его",2],["resolver",2],["reactnode",2],["useissuemessageresolver",2],["шаг",2],["активной",2],["провайдера",2],["которого",2],["записи",2],["каждый",2],["ключ",2],["url",2],["url_protocol",2],["isnumber",2],["integer",2],["nonnegative",2],["nonzero",2],["mindate",2],["maxdate",2],["date_future",2],["maxage",2],["filetype",2],["accept",2],["filename",2],["actualsize",2],["actualtotal",2],["заменяет",2],["только",2],["остаются",2],["накладываются",2],["тот",2],["разрядами",2],["definevalidation",2],["creditform",2],["константа",2],["модуля",2],["ссылка",2],["можно",2],["credit",2],["провайдеры",2],["i18nprovider",2],["formcontrol",2],["useformcontrol",2],["return",2],["ответ",2],["initialpaymenttoohigh",2],["initialpayment",2],["правиле",2],["signatures",2],["flow",2]]},{"id":"compiled-schema","file":"37-compiled-schema.md","title":"Скомпилированная схема — compileSchema, виды узлов и области","purpose":"Авторская схема (`FormSchemaNode`) описывает вид узла ключами: `model`, `item`, `part`, `children`. Разбирать их по-своему раньше приходилось каждому, кто обходил дерево. Теперь дерево разбирает один компилятор — `compileSchema(tree, model)` — и все читают его итог, `CompiledSchema`: у каждого узла явный вид (`kind`), нормализованная привязка к модели, идентификатор и область.","keyConcepts":[],"api":"```typescript\nimport {\n compileSchema,\n isCompiledNode,\n type CompiledSchema,\n type CompiledNode,\n type CompiledChild,\n type SchemaNodeId,\n type SchemaScopeId,\n} from '@reformer/core';\n\nfunction compileSchema(\n tree: FormSchemaNode,\n model?: object, // модель области: корень сборки, под-модель строки или группы\n options?: { scope?: SchemaScopeId } // по умолчанию корень '#'\n): CompiledSchema;\n\nfunction isCompiledNode(value: unknown): value is CompiledNode; // узел компилятора, а не автора\n\ninterface CompiledSchema {\n readonly scope: SchemaScopeId; // '#', '#/address', '#/phones[3]'\n readonly model: object | undefined; // модель области\n readonly root: CompiledNode; // корневой узел области\n readonly nodes: ReadonlyMap<SchemaNodeId, CompiledNode>; // узлы ЭТОЙ области по id\n readonly fields: ReadonlyMap<object, CompiledFieldNode>; // поля по ручке — области и её подформ\n readonly arrays: ReadonlyMap<object, CompiledArrayNode>; // массивы под-форм по ручке\n}\n```","patterns":[],"antiPatterns":[{"why":"Два узла с одним selector в корне — исключение при сборке","bad":"children: [\n { selector: 'address', model: model.$.registrationAddress, part: address },\n { selector: 'address', model: model.$.residenceAddress, part: address },\n];","correctNote":"Разные селекторы; внутри части селекторы свои — повтор в двух частях не конфликтует","correct":"children: [\n { selector: 'registration', model: model.$.registrationAddress, part: address },\n { selector: 'residence', model: model.$.residenceAddress, part: address },\n];"},{"why":"Индекс строки в идентификаторе области: после move/swap он указывает на чужую строку","bad":"const scope = `#/phones[${index}]`;","correctNote":"Ключ строки — FormArray.keyAt(index); он же стоит в скобках области строки","correct":"const scope = `#/phones[${form.phones.keyAt(index)}]`;"},{"why":"Узел строки массива ищется в nodes корня — его там нет","bad":"bundle.render.tree?.nodes.get('co-borrower' as SchemaNodeId); // undefined","correctNote":"Узел строки — в схеме строки; в поведении — applyEach + schema.node(...) строки","correct":"const coBorrowers = bundle.render.tree?.arrays.get(model.$.coBorrowers);\ncoBorrowers?.row(model.coBorrowers.at(0)!).nodes.get('co-borrower' as SchemaNodeId);"},{"why":"Узлы в componentProps — компилятор туда не заглядывает, поле не появится","bad":"{ component: Section, componentProps: { content: { model: model.$.email, component: Input } } }","correctNote":"Вложенные узлы — только в children","correct":"{ component: Section, children: [{ model: model.$.email, component: Input }] }"}],"examples":[],"troubleshooting":[],"seeAlso":["[09-formschema.md](./09-formschema.md) — авторский формат узлов","[35-unified-contract.md](./35-unified-contract.md) — одна схема, одна сборка, одно поведение","[38-form-flow.md](./38-form-flow.md) — шаги потока адресуются `selector` узлов шагов","[10-arrays.md](./10-arrays.md) — массивы под-форм и `keyAt`"],"sections":[{"heading":"Purpose","slug":"purpose-13"},{"heading":"API","slug":"api-10"},{"heading":"Purpose","slug":"purpose-14"},{"heading":"API","slug":"api-11"},{"heading":"Examples","slug":"examples-10"},{"heading":"Anti-patterns","slug":"anti-patterns-11"},{"heading":"See also","slug":"see-also-13"}],"terms":[["model",53],["области",37],["selector",28],["строки",26],["узел",26],["node",26],["part",22],["children",19],["component",19],["без",15],["под",14],["phones",14],["compileschema",13],["узла",13],["kind",13],["schemanodeid",13],["compiledschema",12],["модели",12],["type",12],["scope",12],["readonly",12],["row",12],["массива",12],["модель",11],["части",11],["ключ",11],["узлов",10],["item",10],["tree",10],["compilednode",10],["object",10],["nodes",10],["email",10],["дерево",9],["область",9],["typescript",9],["get",9],["schema",9],["const",9],["схема",8],["bundle",8],["address",8],["узлы",8],["handle",8],["путь",8],["компилятор",7],["render",7],["arrays",7],["ручка",7],["позиционный",7],["compiled",7],["идентификатор",6],["рендерер",6],["schemascopeid",6],["function",6],["корень",6],["группы",6],["value",6],["автора",6],["undefined",6],["root",6],["корневой",6],["readonlymap",6],["поля",6],["массив",6],["array",6],["rowmodel",6],["content",6],["text",6],["componentprops",6],["подформа",6],["coborrowers",6],["при",6],["одна",6],["dev",6],["предупреждение",6],["return",6],["своему",5],["один",5],["его",5],["привязка",5],["createform",5],["compiledchild",5],["from",5],["сборки",5],["или",5],["форм",5],["что",5],["field",5],["строка",5],["registrationaddress",5],["keyat",5],["index",5],["исключение",5],["строк",5],["input",5],["case",5],["виды",4],["formschemanode",4],["вид",4],["читают",4],["дерева",4],["import",4],["iscompilednode",4],["reformer",4],["core",4],["fields",4],["ручке",4],["подформ",4],["массивы",4],["значения",4],["control",4],["container",4],["остальное",4],["текст",4],["только",4],["ребёнок",4],["как",4],["props",4],["затем",4],["контейнер",4],["корня",4],["вложенные",4],["своей",4],["повтор",4],["сборке",4],["селекторы",4],["flow",4],["form",4],["walk",4],["rendernode",4],["ключами",3],["итог",3],["формы",3],["сам",3],["рендереру",3],["инструментам",3],["селекторов",3],["disabled",3],["группа",3],["среди",3],["parts",3],["объект",3],["авторский",3],["поле",3],["рисует",3],["ней",3],["содержимое",3],["внутри",3],["скобках",3],["индекс",3],["move",3],["swap",3],["тот",3],["formarray",3],["одной",3],["путями",3],["обоих",3],["часть",3],["даёт",3],["поведение",3],["шаги",3],["потока",3],["своя",3],["компиляция",3],["out",3],["child",3],["скомпилированная",2],["purpose",2],["авторская",2],["описывает",2],["разбирать",2],["раньше",2],["приходилось",2],["каждому",2],["кто",2],["обходил",2],["теперь",2],["разбирает",2],["все",2],["каждого",2],["явный",2],["нормализованная",2],["компилирует",2],["раз",2],["ноды",2],["одну",2],["компиляцию",2],["она",2],["лежит",2],["нужен",2],["редко",2],["инспектор",2],["проверка",2],["тестах",2],["фабрикам",2],["которые",2],["собирают",2],["форму",2],["api",2]]},{"id":"form-flow","file":"38-form-flow.md","title":"Уровень формы и поток шагов — validate, submit и FormFlow","purpose":"`createForm` возвращает сборку: модель, форму, часть для рендерера и **уровень формы** — проверку, проверку шага, отправку и поток шагов. Уровень один для всех способов реализации — React-руками, рендерер по TS-схеме, рендерер по JSON: сборка одна, поэтому `validate`, отправка (`submission.submit`) и поток ведут себя одинаково.","keyConcepts":[],"api":"```typescript\nimport {\n createForm,\n createFormFlow,\n type FormBundle,\n type FormFlow,\n type FormFlowConfig,\n type FormValidation,\n type FlowTransition,\n type FormSubmission,\n type SubmitResult,\n} from '@reformer/core';\nimport { useFormBundle, useFormFlow } from '@reformer/core/react';\n\n// Конфиг сборки — поля уровня формы\ninterface CreateFormConfig<T> {\n // … initial | model, schema, registry, behavior, seed, setup\n validation?: FormValidation<T> | ValidationDefinition<T>; // голая схема = { rules }\n flow?: FormFlowConfig<T>;\n}\ninterface FormValidation<T> {\n rules?: ValidationDefinition<T>; // полный набор; без него — правила всех шагов + extras\n extras?: ValidationDefinition<T>; // правила вне шагов: проверяются только целиком\n strategy?: 'submit' | 'blur' | 'change' | 'afterFirstSubmit'; // живая проверка, по умолчанию 'submit'\n debounce?: number;\n liveAfterSubmit?: 'change' | 'blur';\n}\ninterface FormFlowConfig<T> {\n steps: readonly { id: string; rules?: ValidationDefinition<T> | null }[];\n}\n\n// Сборка\ninterface FormBundle<T> {\n model: FormModel<T>;\n form: FormGroup<T>;\n render: FormRender; // { tree: CompiledSchema | undefined, controller, node(selector) }\n flow: FormFlow | undefined; // без flow в конфиге — undefined\n validating: ReadonlySignal<boolean>; // идёт полная проверка или проверка шага\n submission: FormSubmission<T>; // отправка — отдельный объект сборки\n // служебное: полный набор правил и контроллер живой проверки (его армирует useFormBundle);\n // без правил — undefined. Методов проверки на нём нет — проверяют validate/validateStep/submission\n validation: { rules: ValidationDefinition<T>; controller: FormValidationController } | undefined;\n validate(): Promise<ValidationResult>; // вся форма, замечания показываются\n validateStep(stepId: string): Promise<ValidationResult>; // неизвестный шаг — исключение\n dispose(): void; // снять сборку; идущие проверки — cancelled, модель остаётся\n}\n\n// Отправка\ninterface FormSubmission<T> {\n submitting: ReadonlySignal<boolean>; // работает обработчик (пока идёт проверка — false)\n submit<R>(handler: (values: T) => R | Promise<R>): Promise<SubmitResult<R>>;\n}\n\ntype SubmitResult<R> =\n | { status: 'submitted'; result: R } // обработчик отработал\n | { status: 'invalid' | 'error' | 'cancelled'; validation: ValidationResult } // обработчик не вызывался\n | { status: 'failed'; error: unknown }; // обработчик бросил исключение\n\n// Поток шагов\ninterface FormFlow {\n readonly steps: readonly string[];\n readonly current: ReadonlySignal<string>;\n readonly index: ReadonlySignal<number>; // с нуля\n readonly completed: ReadonlySignal<ReadonlySet<string>>;\n readonly isFirst: ReadonlySignal<boolean>;\n readonly isLast: ReadonlySignal<boolean>;\n readonly validating: ReadonlySignal<boolean>; // идёт проверка шага в next()\n canGoTo(stepId: string): boolean;\n next(): Promise<FlowTransition>; // проверить правила текущего шага и перейти\n previous(): FlowTransition;\n goTo(stepId: string): FlowTransition; // неизвестный шаг — исключение\n reset(): void; // на первый шаг, пройденные забыты\n}\ntype FlowTransition =\n | { status: 'moved'; from: string; to: string }\n | { status: 'stayed'; step: string; reason: FlowStayReason; validation?: ValidationResult };\ntype FlowStayReason = 'invalid' | 'error' | 'cancelled' | 'edge' | 'locked' | 'current';\n\n// React: снимок потока для рендера\nfunction useFormFlow(flow: FormFlow): {\n current: string; index: number; completed: ReadonlySet<string>;\n isFirst: boolean; isLast: boolean; validating: boolean;\n};\n\n// Поток без сборки\nfunction createFormFlow(options: {\n steps: readonly string[];\n validateStep?: (stepId: string) => Promise<ValidationResult>; // без неё шаги без проверки\n}): FormFlow;\n```","patterns":[],"antiPatterns":[{"why":"Шаги ключами validation.steps — удалено","bad":"createForm({ model, schema, validation: { steps: { loan: loanRules }, extras } });","correctNote":"Шаги — поток сборки; правила вне шагов — extras","correct":"createForm({ model, schema, validation: { extras }, flow: { steps: [{ id: 'loan', rules: loanRules }] } });"},{"why":"Результат проверки как boolean","bad":"if (await credit.validate()) send(); // объект всегда truthy","correctNote":"Статус результата","correct":"if ((await credit.validate()).status === 'valid') send();\n// для отправки — submission.submit: проверка и обработчик одним вызовом\nawait credit.submission.submit(send);"},{"why":"Переход как boolean","bad":"if (await flow.next()) track('step');","correctNote":"Статус перехода","correct":"const transition = await flow.next();\nif (transition.status === 'moved') track(transition.to);"},{"why":"Прыжок вперёд через непройденный шаг — 'locked', поток не двигается","bad":"flow.goTo('confirm');","correctNote":"Вперёд — next() (он проверяет шаг); goTo — назад и на уже открытые шаги","correct":"if (flow.canGoTo('confirm')) flow.goTo('confirm');"},{"why":"Номер шага с единицы — прежний контракт; параметр — строка, номер не скомпилируется","bad":"credit.validateStep(1);","correctNote":"Идентификатор шага — selector узла шага","correct":"credit.validateStep('loan');"},{"why":"Отправка в обход проверки","bad":"await sendApplication(credit.model.get());","correctNote":"bundle.submission.submit: один прогон всех правил, обработчик — только при valid","correct":"await credit.submission.submit(sendApplication);"}],"examples":[],"troubleshooting":[],"seeAlso":["[13-multi-step.md](./13-multi-step.md) — многошаговая форма: правила шагов, визард, раскладка","[28-submit-and-reset.md](./28-submit-and-reset.md) — отправка, сброс, серверные ошибки","[33-validation-strategy.md](./33-validation-strategy.md) — живая проверка: `strategy`, `debounce`","[36-issue-messages.md](./36-issue-messages.md) — тексты замечаний","[37-compiled-schema.md](./37-compiled-schema.md) — `selector`, области и `bundle.render.tree`"],"sections":[{"heading":"Purpose","slug":"purpose-15"},{"heading":"API","slug":"api-12"},{"heading":"Examples","slug":"examples-11"},{"heading":"Anti-patterns","slug":"anti-patterns-12"},{"heading":"See also","slug":"see-also-14"}],"terms":[["flow",37],["const",22],["submit",21],["правила",21],["поток",20],["шага",20],["без",20],["status",19],["submission",18],["credit",18],["next",17],["шаг",16],["проверки",15],["проверка",15],["await",15],["from",14],["string",14],["validatestep",14],["validate",13],["validation",13],["cancelled",13],["обработчик",13],["steps",12],["boolean",12],["typescript",12],["import",12],["type",12],["reformer",12],["шагов",11],["rules",11],["поля",11],["model",11],["stayed",11],["bundle",11],["формы",10],["extras",10],["error",10],["core",10],["сборки",10],["readonly",10],["schema",9],["правил",9],["reason",9],["при",9],["loan",9],["invalid",8],["form",8],["readonlysignal",8],["confirm",8],["createform",7],["модель",7],["отправка",7],["шаги",7],["selector",7],["useformbundle",7],["только",7],["validating",7],["validationresult",7],["замечания",7],["пока",7],["step",7],["outcome",7],["formflow",6],["для",6],["один",6],["interface",6],["undefined",6],["promise",6],["исключение",6],["result",6],["current",6],["goto",6],["valid",6],["creditform",6],["визард",6],["send",6],["transition",6],["button",6],["сборку",5],["react",5],["узла",5],["formflowconfig",5],["formvalidation",5],["flowtransition",5],["useformflow",5],["validationdefinition",5],["или",5],["function",5],["отправки",5],["после",5],["статус",5],["async",5],["showtoast",5],["проверку",4],["полный",4],["набор",4],["вне",4],["createformflow",4],["formbundle",4],["strategy",4],["идёт",4],["stepid",4],["void",4],["isfirst",4],["islast",4],["reset",4],["как",4],["переход",4],["модели",4],["сервера",4],["если",4],["шла",4],["поведения",4],["loanrules",4],["sendapplication",4],["return",4],["response",4],["уровень",3],["форму",3],["всех",3],["схеме",3],["сборка",3],["failed",3],["formsubmission",3],["submitresult",3],["целиком",3],["number",3],["его",3],["форма",3],["неизвестный",3],["остаётся",3],["handler",3],["values",3],["submitted",3],["проверить",3],["previous",3],["moved",3],["edge",3],["locked",3],["потока",3],["вызов",3],["touched",3],["прогон",3],["failures",3],["отвечает",3],["зовёт",3],["получает",3],["сам",3],["runvalidation",3],["всей",3],["замечание",3],["живую",3],["шаге",3],["назад",3],["export",3],["applicant",3],["tsx",3],["createcredit",3],["validationmessagesprovider",3],["else",3],["messages",3],["onclick",3],["defineformbehavior",3],["all",3],["step_rules",3],["возвращает",2],["рендерера",2],["отправку",2],["рендерер",2],["сборке",2],["живой",2],["идентификатор",2],["ошибки",2],["обработчика",2],["api",2],["behavior",2],["проверяются",2],["blur",2],["change",2],["afterfirstsubmit",2],["живая",2],["debounce",2],["render",2],["tree",2],["controller",2],["конфиге",2],["полная",2],["объект",2],["армирует",2],["нет",2],["вся",2],["показываются",2],["dispose",2],["идущие",2],["submitting",2],["работает",2],["отработал",2],["index",2],["completed",2],["readonlyset",2]]}],"symbols":[{"name":"ActionCheck","kind":"interface","signature":"export interface ActionCheck<T> {\n /** Результат проверки. */\n readonly result: ValidationResult;\n /** Значения модели после проверки; при `stale === false` — ровно те, что видели правила. */\n readonly values: T;\n /** Проверка успешна, но модель с тех пор изменилась: её успех относится к прежним значениям. */\n readonly stale: boolean;\n}","summary":"Итог проверки для действия по её результату (отправка, переход шага).","description":"Итог проверки для действия по её результату (отправка, переход шага).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/submission.ts","entries":["."],"package":"@reformer/core"},{"name":"aggregateInto","kind":"function","signature":"export function aggregateInto<TItem extends object>(\n array: ArrayTarget<TItem>,\n derive: (rows: NoInfer<TItem>[]) => Array<{ index: number; patch: Partial<NoInfer<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":"apply","kind":"function","signature":"export function apply<V extends object | null | undefined>(\n targets: GroupTarget<V> | GroupTarget<V>[],\n subSchema: FormBehavior<NoInfer<NonNullable<V>>>\n): 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","unified-contract","form-flow"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors","./validation"],"package":"@reformer/core","usage":5},{"name":"applyEach","kind":"function","signature":"export function applyEach<TItem extends object>(\n array: ArrayTarget<TItem>,\n itemSchema: FormBehavior<NoInfer<TItem>>\n): 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 МАТЕРИАЛИЗОВАН в форме (узел `{ model: model.$.<массив>, 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","common-patterns","common-mistakes","troubleshooting","complete-import","arrays","extended-mistakes","nonexistent-api","array-operations","unified-contract","compiled-schema","form-flow"],"sourcePath":"src/form/behaviors/collections.ts","entries":["./behaviors","./validation"],"package":"@reformer/core","usage":4},{"name":"applyValidationResult","kind":"function","signature":"export function applyValidationResult(\n result: ValidationResult,\n options?: { touch?: boolean }\n): void","summary":"Разнести собранный результат по нодам формы: поставить ошибки проверенным полям и погасить поля, которые были с ошибкой в прошлом разнесённом прогоне той же пары (model, schema), а теперь не проверялись (удалённая строка массива, выключенная ветка).","description":"Разнести собранный результат по нодам формы: поставить ошибки проверенным полям и погасить поля,\nкоторые были с ошибкой в прошлом разнесённом прогоне той же пары (model, schema), а теперь не\nпроверялись (удалённая строка массива, выключенная ветка).\n\nОтменённый результат (`status: 'cancelled'`) не разносится: его async-часть оборвана.\n\n`options.touch`: пометить `touched` ИМЕННО поля, которые схема проверяла (не всё поддерево):\nполя выключенной ветки `validateWhen` и, в проверке уровня формы, выключенные поля не метятся —\nпользователь их не видел, и после включения их ошибка не должна быть видна с первой проверки.\nЗамечания становятся видимыми (`shouldShowIssues`: есть замечания и `touched || dirty`) без\nручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается\n«тронутым».","params":[["result","- Результат {@link runValidation}."],["options","- `{ touch }` — пометить проверенные поля `touched` для показа ошибок."]],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","nonexistent-api","submit-and-reset","async-validator-debounce","form-flow"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"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 * @example\n * ```tsx\n * // Правило массива — в схеме валидации\n * validate(model.$.phones, [minLength(1, { code: 'phones.minLength' })]);\n *\n * // В компоненте\n * const { issues } = useFormControl(phonesArray);\n * // для пустого массива issues содержит замечание с кодом 'phones.minLength'\n * ```\n */\n issues: ValidationIssue[];\n\n /**\n * Собственные замечания массива — только правила на его ручке (`minLength` и т.п.), без\n * замечаний строк. Их показывает ошибка уровня массива (`FieldArray.Error`).\n */\n ownIssues: ValidationIssue[];\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` после правки любого элемента или изменения состава через форму (`push`, `removeAt`,\n * `move`, `setValue`…). Как у поля, запись в модель (загрузка данных) и операция, которая состав\n * не поменяла, изменением не считаются; снимает `reset`.\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` когда массив отключён (`FormArray.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 FormArray}.","description":"Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link FormArray}.\n\nСодержит реактивные данные массива: значения элементов, длину, состояние валидации\nи флаги взаимодействия.","params":[],"example":"Список с динамическим добавлением\n```tsx\ninterface Phone {\ntype: string;\nnumber: string;\n}\n\ninterface Props {\ncontrol: FormArray<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={control.keyAt(index)}\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 issues</p>}\n</div>\n);\n}\n```","examplesCount":1,"related":["useFormControl","FieldControlState"],"topics":["nonexistent-api","type-safety-recipes"],"sourcePath":"src/platforms/react/hooks/types.ts","entries":["./react"],"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(`push({ type: 'house' })`) дополняется из шаблона. Объявляется там же, где остальные\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: 'Анна' }); // { name: 'Анна', phones: [] } — остальное из шаблона\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","complete-import","arrays","project-structure","array-operations","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<ValidationIssue | null>;","summary":"Асинхронное правило поля.","description":"Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный\nпрогон той же схемы отменяет предыдущий).\n\nОтклонённое правило (сеть, исключение) — это сбой проверки, а не «ошибок нет»: прогон получает\nстатус `error`, поле — блокирующую ошибку `ruleFailed`. Если недоступность сервиса не должна\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":"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: FormGroup<T>;\n schema: SchemaScope;\n /**\n * Поток шагов сборки (`createForm({ flow })`); без потока — `undefined`. Один на сборку: его\n * же получают поведения подформ и строк массива.\n */\n flow: FormFlow | undefined;\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","usage":12},{"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":["api-reference","ui-components","nonexistent-api"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["./react"],"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":"CompiledArrayNode","kind":"interface","signature":"export interface CompiledArrayNode extends CompiledNodeBase {\n readonly kind: 'array';\n /** Ручка массива `model.$.<массив>` — привязка фасадом нормализуется к ней. */\n readonly handle: SchemaArrayHandle;\n /** Фасад массива модели: длина, строки и мутации. */\n readonly control: SchemaArrayControl;\n /** Запасной шаблон нового элемента из узла; шаблон модели (`arrayOf`) главнее. */\n readonly initialValue: unknown;\n /**\n * Скомпилированная схема строки — одна на под-модель строки: сборка формы строки и рендерер\n * получают один и тот же объект.\n */\n row(rowModel: object): CompiledSchema;\n}","summary":"Массив под-форм: у каждой строки своя форма и своя область схемы.","description":"Массив под-форм: у каждой строки своя форма и своя область схемы.","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledChild","kind":"type","signature":"export type CompiledChild = CompiledNode | CompiledTextNode;","summary":"Ребёнок контейнера: узел либо текст.","description":"Ребёнок контейнера: узел либо текст.","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledContainerNode","kind":"interface","signature":"export interface CompiledContainerNode extends CompiledNodeBase {\n readonly kind: 'container';\n /** Дети в порядке следования: узлы и текст. */\n readonly children: readonly CompiledChild[];\n}","summary":"Контейнер: узел с детьми либо без привязки — секция, шаг визарда, html-тег.","description":"Контейнер: узел с детьми либо без привязки — секция, шаг визарда, html-тег. Без `component`\nрендерер рисует только детей.","params":[],"examplesCount":0,"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledFieldNode","kind":"interface","signature":"export interface CompiledFieldNode extends CompiledNodeBase {\n readonly kind: 'field';\n /** Ручка значения `model.$.<лист | массив>`. */\n readonly handle: SchemaValueHandle;\n /** Поле создаётся отключённым. */\n readonly disabled: boolean;\n}","summary":"Поле: узел, привязанный к ручке значения — листу или массиву целиком.","description":"Поле: узел, привязанный к ручке значения — листу или массиву целиком.","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledNode","kind":"type","signature":"export type CompiledNode =\n | CompiledFieldNode\n | CompiledArrayNode\n | CompiledPartNode\n | CompiledContainerNode;","summary":"Узел скомпилированной схемы: поле, массив под-форм, подформа или контейнер.","description":"Узел скомпилированной схемы: поле, массив под-форм, подформа или контейнер.","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledPartNode","kind":"interface","signature":"export interface CompiledPartNode extends CompiledNodeBase {\n readonly kind: 'part';\n /** Ручка группы `model.$.<группа>`. */\n readonly handle: SchemaGroupHandle;\n /** Под-модель группы — ключ области подформы. */\n readonly model: object;\n /** Скомпилированное содержимое части. */\n readonly content: CompiledSchema;\n}","summary":"Подформа: часть схемы, подключённая к группе модели.","description":"Подформа: часть схемы, подключённая к группе модели. Содержимое компилируется сразу, в своей\nобласти.","params":[],"examplesCount":0,"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledSchema","kind":"interface","signature":"export interface CompiledSchema {\n /** Идентификатор области. */\n readonly scope: SchemaScopeId;\n /** Модель области: корень сборки, под-модель строки или группы; без модели — `undefined`. */\n readonly model: object | undefined;\n /** Корневой узел области. */\n readonly root: CompiledNode;\n /** Узлы этой области по идентификатору; узлы строк и подформ лежат в их схемах. */\n readonly nodes: ReadonlyMap<SchemaNodeId, CompiledNode>;\n /** Поля по ручке модели — этой области и её подформ (строки массивов — в их схемах). */\n readonly fields: ReadonlyMap<object, CompiledFieldNode>;\n /** Массивы под-форм по ручке модели — этой области и её подформ. */\n readonly arrays: ReadonlyMap<object, CompiledArrayNode>;\n}","summary":"Скомпилированная схема одной области: корня сборки, строки массива или подформы.","description":"Скомпилированная схема одной области: корня сборки, строки массива или подформы.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","formschema","nonexistent-api","unified-contract","compiled-schema","form-flow"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"CompiledTextNode","kind":"interface","signature":"export interface CompiledTextNode {\n readonly kind: 'text';\n /** Позиционный путь первой части. */\n readonly id: SchemaNodeId;\n readonly parts: readonly SchemaTextPart[];\n}","summary":"Текст контейнера: подряд идущие текстовые дети автора, склеенные в один узел.","description":"Текст контейнера: подряд идущие текстовые дети автора, склеенные в один узел.","params":[],"examplesCount":0,"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"compileSchema","kind":"function","signature":"export function compileSchema(\n tree: FormSchemaNode,\n model?: object,\n options?: CompileSchemaOptions\n): CompiledSchema","summary":"Скомпилировать дерево схемы: вид каждого узла, нормализованные привязки, идентификаторы и индексы области.","description":"Скомпилировать дерево схемы: вид каждого узла, нормализованные привязки, идентификаторы и\nиндексы области.\n\nПовтор `selector` в одной области — исключение с путями обоих узлов. Неизвестный ключ узла и\nнераспознанная привязка — предупреждения в dev; такой узел становится контейнером. Вторая\nпривязка той же ручки — тоже предупреждение: нода формы у ручки одна.","params":[["tree","- Корень авторского дерева ({@link FormSchemaNode})."],["model","- Модель области (корень сборки, под-модель строки либо группы): пути вложенных\nобластей считаются от неё; без неё — абсолютные пути ручек (`#/phones.1.address`)."],["options","- {@link CompileSchemaOptions}."]],"returns":"","example":"```typescript\nconst model = createModel({ email: '', phones: [{ number: '' }] });\nconst compiled = compileSchema(\n {\n children: [\n { selector: 'email', model: model.$.email, component: Input },\n { model: model.$.phones, item: (row) => ({ model: row.$.number, component: Input }) },\n ],\n },\n model\n);\n\ncompiled.nodes.get('email' as SchemaNodeId)?.kind; // 'field'\ncompiled.fields.get(model.$.email)?.component; // Input\ncompiled.arrays.get(model.$.phones)?.row(model.phones.at(0)!).scope; // '#/phones[0]'\n```","examplesCount":1,"topics":["api-reference","troubleshooting","complete-import","formschema","nonexistent-api","compiled-schema"],"sourcePath":"src/form/schema/compile.ts","entries":["."],"package":"@reformer/core"},{"name":"CompileSchemaOptions","kind":"interface","signature":"export interface CompileSchemaOptions {\n /** Идентификатор области дерева; по умолчанию корень `#`. */\n readonly scope?: SchemaScopeId;\n}","summary":"Опции {@link compileSchema}.","description":"Опции {@link compileSchema}.","params":[],"examplesCount":0,"sourcePath":"src/form/schema/compile.ts","entries":["."],"package":"@reformer/core"},{"name":"ComponentRef","kind":"type","signature":"export type ComponentRef = string | object;","summary":"Ссылка на компонент узла схемы: то, что слой представления умеет нарисовать.","description":"Ссылка на компонент узла схемы: то, что слой представления умеет нарисовать. Для React это\nкомпонент (функция либо объект `forwardRef`/`memo`) или имя HTML-тега (`'div'`, `'h3'`).\n\nЯдро ссылку не интерпретирует и о слое представления не знает: она доезжает до рендерера как\nесть, а к типу своего UI-слоя её приводит уже он.","params":[],"example":"```ts\nconst byComponent: ComponentRef = Input; // React-компонент\nconst byTag: ComponentRef = 'section'; // HTML-тег\n```","examplesCount":1,"topics":["api-reference","api-signatures","nonexistent-api"],"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"compute","kind":"function","signature":"export function compute<R>(\n target: Signal<R>,\n read: () => NoInfer<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","form-types","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","submit-and-reset","type-safety-recipes","unified-contract"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":21},{"name":"computeFrom","kind":"function","signature":"export function computeFrom<R>(\n sources: ReadonlySignal<any>[],\n target: Signal<R>,\n fn: (...values: any[]) => NoInfer<R>,\n options?: { when?: (...values: any[]) => boolean }\n): BehaviorCleanup","summary":"Вычисляемое поле: `target = fn(...sourceValues)` при изменении источников.","description":"Вычисляемое поле: `target = fn(...sourceValues)` при изменении источников.\n\nПримитив модели: цель вычисляемой НЕ помечает. Поэтому массовая запись (`model.set`,\n`model.patch`, `model.reset`) её перезаписывает, а `isDirty()` её учитывает — до следующей смены\nисточников. Оператор поведения `computeFrom` из `@reformer/core/behaviors` цель помечает\n(`markDerived`) и защищён от циклов: массовая запись такую цель не трогает. В поведении формы\nнужен оператор; примитив — для модели без формы.","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","nonexistent-api","reading-values","compute-vs-watch","cycle-detection","submit-and-reset","type-safety-recipes"],"sourcePath":"src/model/behaviors-value.ts","entries":["./model","./behaviors"],"package":"@reformer/core","usage":4},{"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","nonexistent-api","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":8},{"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`). Сравнение версий такой случай пропустило бы.","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":"createForm","kind":"function","signature":"export function createForm<T extends object>(config: CreateFormConfig<T>): FormBundle<T>","summary":"Собрать модель, форму, уровень формы (проверка, отправка, поток шагов) и дерево для рендера за один проход.","description":"Собрать модель, форму, уровень формы (проверка, отправка, поток шагов) и дерево для рендера за\nодин проход.","params":[["config","- {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,\n`behavior`, `validation`, `flow`, `seed`, `setup`."]],"returns":"","example":"Рендерер по TS-схеме\n```tsx\nconst credit = useFormBundle(() =>\ncreateForm<CreditForm>({\nmodel: createCreditModel(),\nschema: creditSchema, // (model) => узел\nbehavior: creditBehavior,\nvalidation: { extras: crossRules },\nflow: { steps: [{ id: 'loan', rules: loanRules }, { id: 'confirm' }] },\n})\n);\nreturn <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;\n```","examplesCount":3,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","formschema","arrays","multi-step","extended-mistakes","project-structure","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","unified-contract","compiled-schema","form-flow"],"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":41},{"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 /**\n * Правила валидации: готовый набор либо {@link FormValidation} — полный набор `rules`, правила\n * вне шагов `extras` и стратегия живой проверки. Без `rules` полный набор собирается из правил\n * шагов потока и `extras`.\n */\n validation?: FormValidation<T> | ValidationDefinition<T>;\n /**\n * Поток шагов: `{ steps: [{ id, rules }] }`. Идентификатор шага — `selector` узла шага в схеме;\n * `next()` потока проверяет правила текущего шага.\n */\n flow?: FormFlowConfig<T>;\n /**\n * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту\n * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на\n * инициализации не срабатывают). Модель, созданная из `initial`, снимает точку отсчёта после\n * `seed` — сброс его правки не откатывает; точкой отсчёта переданной `model` управляет владелец.\n */\n seed?: (model: FormModel<T>) => void;\n /** Донастройка ПОСЛЕ сборки: получает готовый бандл. */\n setup?: (bundle: FormBundle<T>) => void;\n}","summary":"Конфиг {@link createForm}.","description":"Конфиг {@link createForm}.","params":[],"examplesCount":0,"topics":["nonexistent-api","form-flow"],"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"createFormFlow","kind":"function","signature":"export function createFormFlow(options: FormFlowOptions): FormFlow","summary":"Создать поток шагов.","description":"Создать поток шагов.","params":[["options","- {@link FormFlowOptions}: шаги и проверка шага."]],"returns":"","example":"```typescript\nconst flow = createFormFlow({ steps: ['loan', 'contacts', 'confirm'] });\n\nawait flow.next(); // { status: 'moved', from: 'loan', to: 'contacts' }\nflow.canGoTo('confirm'); // false — шаг contacts ещё не пройден\nflow.goTo('loan'); // { status: 'moved', from: 'contacts', to: 'loan' }\n```","examplesCount":1,"topics":["api-reference","complete-import","multi-step","form-flow"],"sourcePath":"src/form/flow/flow.ts","entries":["."],"package":"@reformer/core"},{"name":"createFormFromModel","kind":"function","signature":"export function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormGroup<T>","summary":"Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика).","description":"Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика).\n\nЗначения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и\nссылаются на сигналы модели по идентичности. Обходит структуру модели и строит ноды по виду её\nузлов, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы\nпод-форм ({@link FormArray}, на любой глубине), заполняет реестр ручка→нода (для\n`enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное\nповедение (cleanup живёт на форме).\n\nОбычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту\nфабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента\nмассива, тесты, свой слой сборки.","params":[["args","- Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}"]],"returns":"Группа формы {@link FormGroup} с нодами полей по именам","example":"Форма из модели и схемы\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; правила — в отдельной ValidationDefinition.\n{ model: model.$.email, component: Input },\n// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)\n{ model: 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-reference","api-signatures","complete-import","formschema","arrays","nonexistent-api","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 * Схема — дерево узлов {@link FormSchemaNode}. Сборка обходит её и привязывает конфиг поля к\n * ноде по идентичности ручки (`node.model === model.$.<path>`). Опциональна.\n */\n schema?: FormSchemaNode;\n /**\n * Декларативная схема поведения ({@link defineFormBehavior}). Запускается ПОСЛЕ построения нод и\n * заполнения реестра сигнал→нода; cleanup живёт на форме и снимается вместе с ней.\n */\n behavior?: FormBehavior<T>;\n /**\n * Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт\n * сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.\n * Без него поведение пишет правила в собственный контроллер, который никто не читает.\n */\n schemaController?: SchemaController;\n /** Поток шагов сборки: его получает поведение (`defineFormBehavior(({ flow }) => …)`). */\n flow?: FormFlow;\n}","summary":"Аргументы сборки формы из модели: данные приходят из {@link FormModel}, конфиг полей (component/componentProps) — из единой схемы.","description":"Аргументы сборки формы из модели: данные приходят из {@link FormModel},\nконфиг полей (component/componentProps) — из единой схемы.","params":[],"examplesCount":0,"sourcePath":"src/form/create-form.ts","entries":["."],"package":"@reformer/core"},{"name":"createFormLevel","kind":"function","signature":"export function createFormLevel<T>(\n model: FormModel<T>,\n config: {\n validation?: FormValidation<T> | ValidationDefinition<T>;\n flow?: FormFlowConfig<T>;\n }\n): FormLevel<T>","summary":"Собрать уровень формы: правила, контроллер живой проверки, поток шагов и отправку.","description":"Собрать уровень формы: правила, контроллер живой проверки, поток шагов и отправку.","params":[["model","- Модель формы."],["config","- Правила (`validation`) и поток (`flow`)."]],"returns":"","example":"```typescript\nconst level = createFormLevel(model, {\n validation: { extras: crossRules },\n flow: { steps: [{ id: 'loan', rules: loanRules }, { id: 'confirm' }] },\n});\nconst result = await level.submission.submit(sendApplication);\nif (result.status === 'invalid') showErrors();\n```","examplesCount":1,"topics":["api-reference","nonexistent-api","submit-and-reset"],"sourcePath":"src/form/form-level.ts","entries":["."],"package":"@reformer/core"},{"name":"createFormValidation","kind":"function","signature":"export function createFormValidation<T>(\n model: FormModel<T>,\n schema: ValidationDefinition<T>,\n options: ValidationStrategyOptions = {}\n): FormValidationController","summary":"Собрать контроллер валидации формы с выбранной стратегией запуска.","description":"Собрать контроллер валидации формы с выбранной стратегией запуска. Движок прогона — тот же, что у\n`validateModel`.\n\nФабрика ЧИСТАЯ до `start()` (никаких подписок) — безопасна для SSR/headless. Реактивные триггеры\nарминуются только в `start()` (в React — из `useEffect`).","params":[["model","- Модель данных."],["schema","- Схема валидации. **Стабильная ссылка** (иначе устаревший прогон не отменится)."],["options","- {@link ValidationStrategyOptions}."]],"example":"```ts\nconst ctrl = createFormValidation(model, schema, { strategy: 'afterFirstSubmit', debounce: 300 });\nconst dispose = ctrl.start(); // на клиенте\nconst ok = await ctrl.validate(); // проверка: замечания проверенных полей\nconst sent = await ctrl.run({ touch: 'all', submit: true }); // попытка отправки: живая фаза\ndispose();\n```","examplesCount":1,"topics":["api-reference","api-signatures","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":["api-reference","complete-import","i18n","issue-messages"],"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":["api-reference","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":"Создать реактивную модель данных формы.","description":"Создать реактивную модель данных формы.","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","api-signatures","common-mistakes","complete-import","form-types","formschema","arrays","project-structure","nonexistent-api","conditional-fields","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy","unified-contract","compiled-schema"],"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":"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":"createSubmission","kind":"function","signature":"export function createSubmission<T>(dependencies: SubmissionDependencies<T>): FormSubmission<T>","summary":"Создать отправку формы.","description":"Создать отправку формы.\n\nОбычно её создаёт сборка (`createForm` → `bundle.submission`). Прямой вызов нужен там, где\nсборки нет: визард в разметке без `createForm` отправляет значения формы без проверки.","params":[["dependencies","- {@link SubmissionDependencies}: проверка перед действием и значения."]],"returns":"","example":"Без проверки — значения формы как есть\n```typescript\nconst submission = createSubmission<Profile>({ check: undefined, values: () => form.getValue() });\nconst outcome = await submission.submit(api.save);\nif (outcome.status === 'failed') showToast('Не удалось сохранить');\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","submit-and-reset"],"sourcePath":"src/form/submission.ts","entries":["."],"package":"@reformer/core"},{"name":"deepEqual","kind":"const","signature":"export const deepEqual","summary":"Глубокое сравнение значений модели: объекты и массивы — по содержимому, даты — по времени, `NaN` равен себе, остальное (включая файлы) — по ссылке.","description":"Глубокое сравнение значений модели: объекты и массивы — по содержимому, даты — по времени,\n`NaN` равен себе, остальное (включая файлы) — по ссылке.\n\nТо же сравнение стоит за `isDirty` модели и за сверкой «модель изменилась, пока шла проверка»\nпри отправке и переходе шага: значение, изменённое и возвращённое, изменением не считается.","params":[],"example":"```typescript\ndeepEqual({ date: new Date(0), tags: ['a'] }, { date: new Date(0), tags: ['a'] }); // true\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import"],"sourcePath":"src/model/model-nodes.ts","entries":["./model"],"package":"@reformer/core"},{"name":"DeepPartial","kind":"type","signature":"export type DeepPartial<T> = {\n [K in keyof T]?: DeepPartialValue<T[K]>;\n};","summary":"Частичное значение модели для {@link ModelApi.patch}: любой ключ можно опустить на любой глубине вложенных групп.","description":"Частичное значение модели для {@link ModelApi.patch}: любой ключ можно опустить на любой\nглубине вложенных групп. Массив передаётся целиком — он заменяется, а не сливается.","params":[],"example":"```typescript\ntype Profile = { name: string; address: { city: string; street: string }; tags: string[] };\nconst update: DeepPartial<Profile> = { address: { city: 'Казань' } };\n```","examplesCount":1,"topics":["submit-and-reset","async-preload"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"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». Внутри схемы\nповедения (в `setup`, в её effect'ах и отложенных заданиях) запись отменяется снятием схемы.","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core"},{"name":"deferredWritesSettled","kind":"function","signature":"export function deferredWritesSettled(): Promise<void> | undefined","summary":"Дождаться, пока отработают отложенные записи текущего тика — вместе с теми, что они породили (цепочка расчётов: каждая запись будит следующий расчёт, а тот снова пишет отложенно).","description":"Дождаться, пока отработают отложенные записи текущего тика — вместе с теми, что они породили\n(цепочка расчётов: каждая запись будит следующий расчёт, а тот снова пишет отложенно).\n\nЖдёт только микротаски: запись после таймера (debounce) или после `await` в async-callback сюда не\nвходит — это уже не «тот же тик».\n\nСчётчик общий для рантайма, а не для одной модели: отложенные записи ставит {@link runOutsideEffect},\nа он моделей не различает. Нужен проверкам «по действию» — отправке и переходу шага: правила должны\nувидеть модель после записей поведения того же тика (колбэк `onChange`, сброс `resetOnDisable`).","params":[],"returns":"Промис ожидания; `undefined` — отложенных записей сейчас нет, ждать нечего.","example":"```typescript\nmodel.loanType = 'mortgage'; // поведение допишет зависимые поля на микротаске\nawait deferredWritesSettled();\nconst values = model.get(); // уже с записями поведения\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import"],"sourcePath":"src/model/safe-effect.ts","entries":["./model"],"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","multi-step","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","form-flow"],"sourcePath":"src/form/behaviors/context.ts","entries":["./behaviors"],"package":"@reformer/core","usage":13},{"name":"defineIssueMessages","kind":"function","signature":"export function defineIssueMessages<const Messages extends IssueMessages>(\n messages: Messages\n): Messages","summary":"Объявить словарь формы.","description":"Объявить словарь формы. Возвращает аргумент как есть — функция нужна типам: коды остаются\nлитералами, а значения проверяются как {@link IssueMessage}.","params":[],"example":"```ts\nexport const loanMessages = defineIssueMessages({\n 'loanAmount.required': 'Укажите сумму кредита',\n 'loanPurpose.minLength': 'Минимум {minLength} символов',\n 'carYear.max': (params) => `Не позднее ${String(params.max)}`,\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","project-structure","ui-components","nonexistent-api","submit-and-reset","type-safety-recipes","i18n","unified-contract","issue-messages"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"package":"@reformer/core","usage":18},{"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":"defineValidation","kind":"function","signature":"export function defineValidation<T>(schema: ValidationDefinition<T>): ValidationDefinition<T>","summary":"Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`).","description":"Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.","params":[],"example":"```ts\nexport const step1 = defineValidation<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","issue-messages","form-flow"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"package":"@reformer/core","usage":41},{"name":"disableWhen","kind":"function","signature":"export function disableWhen(\n target: EnableTarget | EnableTarget[],\n condition: () => boolean,\n options?: { resetOnDisable?: boolean }\n): void","summary":"Условное выключение поля(ей) (инверсия {@link enableWhen}).","description":"Условное выключение поля(ей) (инверсия {@link enableWhen}).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-mistakes","troubleshooting","complete-import","nonexistent-api","conditional-fields"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":4},{"name":"disposeForm","kind":"function","signature":"export function disposeForm<T>(form: FormGroup<T>): void","summary":"Снять форму, собранную {@link createFormFromModel}: поведение формы и подписки нод (массивы под-форм следят за моделью).","description":"Снять форму, собранную {@link createFormFromModel}: поведение формы и подписки нод (массивы\nпод-форм следят за моделью). Значения модели остаются. Идемпотентно.\n\nСборку одним вызовом снимают целиком — `bundle.dispose()`: вместе с формой она снимает живую\nпроверку и отменяет идущие проверки. Этот вызов нужен своим слоям сборки поверх\n{@link createFormFromModel}.","params":[["form","- Форма из {@link createFormFromModel}."]],"example":"```typescript\nconst form = createFormFromModel<Profile>({ model, schema, behavior });\n// … форма больше не нужна\ndisposeForm(form);\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","nonexistent-api","submit-and-reset"],"sourcePath":"src/form/create-form.ts","entries":["."],"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(`{ model: 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(options?: RuleOptions): Rule<string | null | undefined>","summary":"Фабрика валидатора формата email.","description":"Фабрика валидатора формата email.\n\nПроверяет по упрощённому regex `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`. Пустые значения\n(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для строкового поля","example":"Проверка формата email\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, email } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.email, [required(), email({ code: 'email.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","unified-contract","issue-messages","compiled-schema"],"sourcePath":"src/form/validators/email.ts","entries":["./validators"],"package":"@reformer/core","usage":108},{"name":"enableWhen","kind":"function","signature":"export function enableWhen(\n target: EnableTarget | EnableTarget[],\n condition: () => boolean,\n options?: { resetOnDisable?: boolean }\n): void","summary":"Условное включение поля(ей) — скаляр, массив целей или группа.","description":"Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию.","params":[],"examplesCount":0,"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/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":14},{"name":"exclusiveFlag","kind":"function","signature":"export function exclusiveFlag<TItem extends object>(\n array: ArrayTarget<TItem>,\n getFlag: (row: FormModel<NoInfer<TItem>>) => Signal<boolean>\n): void","summary":"Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).","description":"Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).\nКогда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:\nновые строки с флагом false исключения не запускают.\n\nНачальные данные и добавленные строки флаг не МЕНЯЮТ — он приходит сразу `true`. Такая строка\nпрежний выбор не снимает: флаг сбрасывается у неё. Из нескольких пришедших с `true` сразу\n(начальные данные) остаётся первая по порядку строк.","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":["api-reference","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":["./react"],"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 * issues/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":["api-reference","ui-components","nonexistent-api"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["./react"],"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":["./react"],"package":"@reformer/core"},{"name":"FieldConfig","kind":"interface","signature":"export interface FieldConfig<T> {\n /**\n * Сигнал значения из {@link FormModel} — источник истины значения поля. Нода значением не\n * владеет, а ссылается на этот сигнал.\n */\n valueSignal: Signal<T>;\n /**\n * UI-компонент поля. Опционален: core-часть можно использовать без ссылки на компонент\n * (значение и состояние работают без UI; компонент нужен только для рендеринга).\n */\n component?: ComponentRef;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n componentProps?: any;\n disabled?: boolean;\n}","summary":"Конфигурация поля формы ({@link FormControl}): сигнал значения из модели и то, что нода берёт из узла схемы.","description":"Конфигурация поля формы ({@link FormControl}): сигнал значения из модели и то, что нода берёт\nиз узла схемы.\n\nВ схеме формы поле записывают узлом `{ model: model.$.field, component, componentProps }`;\nсборка `createFormFromModel` превращает узел в этот конфиг сама. Правил валидации в конфиге нет —\nони живут в схеме валидации (`defineValidation`, `@reformer/core/validation`).","params":[],"examplesCount":0,"topics":["arrays","nonexistent-api","array-operations"],"sourcePath":"src/form/types/field-config.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 { issues } = useFormControl(field);\n * const message = useIssueMessage(); // из '@reformer/core/react'\n *\n * return (\n * <ul className=\"error-list\">\n * {issues.map((error, i) => (\n * <li key={i}>{message(error)}</li>\n * ))}\n * </ul>\n * );\n * ```\n */\n issues: ValidationIssue[];\n\n /**\n * Флаг валидности поля.\n * `true` когда поле прошло все валидации (issues.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` когда есть ошибки валидации (issues.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 * Показывать ли состояние ошибки — `aria-invalid`, рамку: поле невалидно и его трогали или\n * меняли. Предупреждение (`severity: 'warning'`) поле невалидным не делает, поэтому для текстов\n * замечаний есть {@link FieldControlState.shouldShowIssues}.\n *\n * @example\n * ```tsx\n * const { shouldShowError } = useFormControl(field);\n *\n * return <input {...props} aria-invalid={shouldShowError} />;\n * ```\n */\n shouldShowError: boolean;\n\n /**\n * Показывать ли тексты замечаний: у поля есть замечания — ошибки или предупреждения, — и его\n * трогали или меняли.\n *\n * @example\n * ```tsx\n * const { shouldShowError, shouldShowIssues, issues } = useFormControl(field);\n * const message = useIssueMessage(); // из '@reformer/core/react'\n *\n * return (\n * <div>\n * <input {...props} aria-invalid={shouldShowError} />\n * {shouldShowIssues &&\n * issues.map((issue) => (\n * <span key={issue.code} className={issue.severity === 'warning' ? 'warning' : 'error'}>\n * {message(issue)}\n * </span>\n * ))}\n * </div>\n * );\n * ```\n */\n shouldShowIssues: boolean;\n\n /**\n * Пользовательские props для передачи в UI-компоненты.\n * Устанавливаются через {@link FormControl.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 FormControl}.","description":"Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FormControl}.\n\nСодержит реактивные данные поля: значение, состояние валидации, флаги взаимодействия\nи пользовательские props для компонентов.","params":[],"example":"Базовое использование\n```tsx\ninterface Props {\ncontrol: FormControl<string>;\n}\n\nfunction TextField({ control }: Props) {\nconst state = useFormControl(control);\nconst message = useIssueMessage(); // из '@reformer/core/react'\n\nreturn (\n<div>\n<input\nvalue={state.value}\ndisabled={state.disabled}\nonChange={e => control.setValue(e.target.value)}\n/>\n{state.shouldShowIssues && state.issues[0] && (\n<span className=\"error\">{message(state.issues[0])}</span>\n)}\n</div>\n);\n}\n```","examplesCount":1,"related":["useFormControl","ArrayControlState"],"topics":["api-reference","complete-import","nonexistent-api"],"sourcePath":"src/platforms/react/hooks/types.ts","entries":["./react"],"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":["api-reference","ui-components","nonexistent-api"],"sourcePath":"src/platforms/react/field-binding/field-handle.ts","entries":["./react"],"package":"@reformer/core","usage":4},{"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":["./react"],"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(accept: string, options?: RuleOptions): Rule<unknown>","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 RuleOptions}). В `params` ошибки автоматически\nпопадают `accept` и `fileName` (первый нарушивший файл)."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Только изображения и PDF\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { fileType } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [fileType('image/*,.pdf', { code: 'documents.fileType' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","issue-messages"],"sourcePath":"src/form/validators/file-type.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"FlowStayReason","kind":"type","signature":"export type FlowStayReason = 'invalid' | 'error' | 'cancelled' | 'edge' | 'locked' | 'current';","summary":"Почему поток остался на шаге: `invalid` — у шага блокирующие замечания, `error` — правило не вернуло результат, `cancelled` — прогон отменён либо результат проверки устарел (поток перевели, модель формы изменилась, пока шла проверка шага), `edge` — дальше (или раньше) шагов нет, `locked` — шаг недос","description":"Почему поток остался на шаге: `invalid` — у шага блокирующие замечания, `error` — правило не\nвернуло результат, `cancelled` — прогон отменён либо результат проверки устарел (поток перевели,\nмодель формы изменилась, пока шла проверка шага), `edge` — дальше (или раньше) шагов нет,\n`locked` — шаг недоступен, пока не пройден предыдущий, `current` — `goTo` на шаг, на котором поток\nуже стоит.","params":[],"examplesCount":0,"topics":["form-flow"],"sourcePath":"src/form/flow/flow.ts","entries":["."],"package":"@reformer/core"},{"name":"FlowStep","kind":"interface","signature":"export interface FlowStep<T> {\n readonly id: string;\n /** Правила шага; `null` или без поля — шаг без правил. */\n readonly rules?: ValidationDefinition<T> | null;\n}","summary":"Шаг потока: идентификатор (он же `selector` узла шага в схеме) и правила шага.","description":"Шаг потока: идентификатор (он же `selector` узла шага в схеме) и правила шага.","params":[],"examplesCount":0,"topics":["project-structure"],"sourcePath":"src/form/form-level.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"FlowTransition","kind":"type","signature":"export type FlowTransition =\n | { readonly status: 'moved'; readonly from: string; readonly to: string }\n | {\n readonly status: 'stayed';\n readonly step: string;\n readonly reason: FlowStayReason;\n readonly validation?: ValidationResult;\n };","summary":"Итог перехода: поток перешёл на другой шаг либо остался — с причиной и, если шаг проверялся, результатом проверки.","description":"Итог перехода: поток перешёл на другой шаг либо остался — с причиной и, если шаг проверялся,\nрезультатом проверки.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","multi-step","type-safety-recipes","form-flow"],"sourcePath":"src/form/flow/flow.ts","entries":["."],"package":"@reformer/core"},{"name":"FormArray","kind":"interface","signature":"export interface FormArray<T extends object> extends FormNode<T[]> {\n /** Число строк; чтение в эффекте или `computed` подписывает на изменения. */\n readonly length: number;\n at(index: number): FormGroup<T> | undefined;\n map<R>(callback: (row: FormGroup<T>, index: number) => R): R[];\n forEach(callback: (row: FormGroup<T>, index: number) => void): void;\n /** Стабильный ключ строки: держится за элемент модели и не меняется при `move` и `swap`. */\n keyAt(index: number): string;\n /** Добавить строку; без значения — по шаблону массива модели (`arrayOf(blank)`). */\n push(item?: T): void;\n insert(index: number, item?: T): void;\n removeAt(index: number): void;\n move(from: number, to: number): void;\n swap(first: number, second: number): void;\n clear(): void;\n}","summary":"Массив под-форм: строка — {@link FormGroup} над элементом массива модели.","description":"Массив под-форм: строка — {@link FormGroup} над элементом массива модели.\n\nДанными массив не владеет: мутации уходят в массив модели, формы строк держатся за под-модели\nи при `move`/`swap` не пересоздаются.\n\nИндексы (`at`, `keyAt`, `insert`, `removeAt`, `move`, `swap`) — по строкам формы. Элемент `null`\nв массиве модели строкой не становится, и индекс строки может не совпасть с индексом модели.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-mistakes","complete-import","form-types","formschema","arrays","nonexistent-api","array-operations","type-safety-recipes","unified-contract","compiled-schema"],"sourcePath":"src/form/types/nodes.ts","entries":["."],"package":"@reformer/core","usage":2},{"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 * никто не читает (модель без рендерера). `flow` — поток шагов сборки.\n */\n __run(\n model: FormModel<T>,\n form: FormGroup<T>,\n controller?: SchemaController,\n flow?: FormFlow\n ): 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> extends FormLevel<T> {\n readonly model: FormModel<T>;\n readonly form: FormGroup<T>;\n readonly render: FormRender;\n /**\n * Снять сборку: поведение формы, подписки нод, живую проверку; идущие проверки — полная, шага,\n * отправки — получают `cancelled`. Значения модели остаются. Идемпотентно.\n *\n * Владелец сборки — тот, кто вызвал `createForm`: вне React (тесты, сервер, своя оболочка)\n * снимайте её этим вызовом, когда форма больше не нужна.\n */\n dispose(): void;\n}","summary":"Результат {@link createForm}: модель, форма, часть для рендерера и уровень формы ({@link FormLevel}) — `validate()`, `validateStep(id)`, поток шагов и отправка `submission.submit(handler)`.","description":"Результат {@link createForm}: модель, форма, часть для рендерера и уровень формы\n({@link FormLevel}) — `validate()`, `validateStep(id)`, поток шагов и отправка\n`submission.submit(handler)`.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","nonexistent-api","form-flow"],"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":6},{"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,"topics":["api-reference","nonexistent-api"],"sourcePath":"src/platforms/react/form-bundle-context.ts","entries":["./react"],"package":"@reformer/core"},{"name":"FormBundleLike","kind":"interface","signature":"export interface FormBundleLike {\n validation?: { controller: FormValidationController };\n /** Снять сборку: поведение, подписки нод, живую проверку. Есть у всех фабрик семейства. */\n dispose?(): void;\n}","summary":"Минимум, который хук ожидает от бандла: контроллер живой валидации и снятие — оба опционально.","description":"Минимум, который хук ожидает от бандла: контроллер живой валидации и снятие — оба опционально.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/hooks/use-form-bundle.ts","entries":["./react"],"package":"@reformer/core"},{"name":"FormControl","kind":"interface","signature":"export interface FormControl<T> extends FormNode<T> {\n /** Компонент поля из узла схемы. */\n readonly component: ComponentRef | undefined;\n /** Пропсы компонента: из узла схемы и правок {@link FormControl.updateComponentProps}. */\n readonly componentProps: ReadonlySignal<Readonly<Record<string, unknown>>>;\n /**\n * Показывать ли состояние ошибки (`aria-invalid`, рамка): поле невалидно и его трогали или\n * меняли. Предупреждение (`severity: 'warning'`) поле невалидным не делает — тексты замечаний\n * показывают по {@link FormControl.shouldShowIssues}.\n */\n readonly shouldShowError: ReadonlySignal<boolean>;\n /**\n * Показывать ли тексты замечаний: у поля есть замечания — ошибки или предупреждения, — и его\n * трогали или меняли. Условия те же, что у {@link FormControl.shouldShowError} (не выключено,\n * проверка не идёт), но в счёт идёт любое замечание, а не только блокирующее.\n */\n readonly shouldShowIssues: ReadonlySignal<boolean>;\n /** Слить правку в пропсы компонента (например, опции после загрузки справочника). */\n updateComponentProps(patch: Record<string, unknown>): void;\n}","summary":"Поле формы: нода над листом модели и состояние его компонента.","description":"Поле формы: нода над листом модели и состояние его компонента.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-mistakes","complete-import","form-types","formschema","arrays","ui-components","nonexistent-api","type-safety-recipes","i18n","unified-contract","issue-messages"],"sourcePath":"src/form/types/nodes.ts","entries":["."],"package":"@reformer/core","usage":7},{"name":"FormFields","kind":"type","signature":"export type FormFields<T> =\n IsAny<T> extends true\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Record<string, any>\n : {\n [K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<infer U>\n ? IsGroupObject<U> extends true\n ? FormArray<U & object>\n : FormControl<T[K]>\n : IsGroupObject<NonNullable<T[K]>> extends true\n ? FormGroup<NonNullable<T[K]>>\n : FormControl<T[K]>;\n };","summary":"Ноды полей группы по именам полей данных: массив объектов — {@link FormArray}, объект — {@link FormGroup}, остальное (примитивы, массивы примитивов, даты, файлы) — {@link FormControl}.","description":"Ноды полей группы по именам полей данных: массив объектов — {@link FormArray}, объект —\n{@link FormGroup}, остальное (примитивы, массивы примитивов, даты, файлы) — {@link FormControl}.\nОпциональность снята: нода есть у каждого поля, опционально лишь значение.\n\n`FormFields<any>` — нестрогий словарь: так группа любой формы приводится к `FormGroup<any>`\n(контекст React, обобщённые компоненты), а не упирается в отображение полей типа `any`.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","type-safety-recipes"],"sourcePath":"src/form/types/nodes.ts","entries":["."],"package":"@reformer/core"},{"name":"FormFlow","kind":"interface","signature":"export interface FormFlow {\n /** Идентификаторы шагов по порядку. */\n readonly steps: readonly string[];\n /** Текущий шаг. */\n readonly current: ReadonlySignal<string>;\n /** Номер текущего шага с нуля. */\n readonly index: ReadonlySignal<number>;\n /** Пройденные шаги: правила шага прошли при переходе дальше. */\n readonly completed: ReadonlySignal<ReadonlySet<string>>;\n readonly isFirst: ReadonlySignal<boolean>;\n readonly isLast: ReadonlySignal<boolean>;\n /** Идёт ли проверка шага в `next()`. */\n readonly validating: ReadonlySignal<boolean>;\n /** Можно ли перейти на шаг: первый — всегда, остальные — после прохождения предыдущего. */\n canGoTo(stepId: string): boolean;\n /** Проверить правила текущего шага и при успехе перейти к следующему. */\n next(): Promise<FlowTransition>;\n /** Вернуться на шаг назад; пройденными остаются только шаги до него. */\n previous(): FlowTransition;\n /** Перейти на шаг; неизвестный шаг — исключение. */\n goTo(stepId: string): FlowTransition;\n /** Вернуться на первый шаг и забыть пройденные. */\n reset(): void;\n}","summary":"Поток шагов формы.","description":"Поток шагов формы.","params":[],"examplesCount":0,"topics":["api-reference","common-mistakes","troubleshooting","complete-import","form-flow"],"sourcePath":"src/form/flow/flow.ts","entries":["."],"package":"@reformer/core"},{"name":"FormFlowConfig","kind":"interface","signature":"export interface FormFlowConfig<T> {\n readonly steps: readonly FlowStep<T>[];\n}","summary":"Поток шагов в конфиге сборки.","description":"Поток шагов в конфиге сборки.","params":[],"examplesCount":0,"topics":["api-reference","complete-import","project-structure","form-flow"],"sourcePath":"src/form/form-level.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"FormFlowOptions","kind":"interface","signature":"export interface FormFlowOptions {\n /** Идентификаторы шагов по порядку; не пустой список без повторов. */\n readonly steps: readonly string[];\n /** Проверка шага при `next()`; без неё шаг проходится без проверки. */\n readonly validateStep?: (stepId: string) => Promise<ValidationResult>;\n}","summary":"Опции {@link createFormFlow}.","description":"Опции {@link createFormFlow}.","params":[],"examplesCount":0,"sourcePath":"src/form/flow/flow.ts","entries":["."],"package":"@reformer/core"},{"name":"FormFlowSnapshot","kind":"interface","signature":"export interface FormFlowSnapshot {\n /** Текущий шаг. */\n readonly current: string;\n /** Номер текущего шага с нуля. */\n readonly index: number;\n /** Пройденные шаги. */\n readonly completed: ReadonlySet<string>;\n readonly isFirst: boolean;\n readonly isLast: boolean;\n /** Идёт ли проверка шага. */\n readonly validating: boolean;\n}","summary":"Состояние потока на момент рендера.","description":"Состояние потока на момент рендера.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/hooks/use-form-flow.ts","entries":["./react"],"package":"@reformer/core"},{"name":"FormGroup","kind":"type","signature":"export type FormGroup<T> = FormNode<T> &\n Omit<FormFields<T>, keyof FormNode<T>> & {\n /** Ноды всех полей по имени, включая затенённые членами {@link FormNode}. */\n readonly $: FormFields<T>;\n };","summary":"Группа полей: нода над группой модели и доступ к нодам полей.","description":"Группа полей: нода над группой модели и доступ к нодам полей.\n\n`form.email` — нода поля; поле с именем члена {@link FormNode} (`value`, `issues`, `reset`, …)\nнедостижимо точкой и доступно через `form.$`: `form.$.value`. Остальные имена полей данных\n(`id`, `status`, `fields`, `submit`, …) ничем не затенены.","params":[],"example":"```typescript\ninterface Order { status: string; value: number; customer: { name: string } }\nconst { form } = createForm<Order>({ initial: { status: 'new', value: 0, customer: { name: '' } } });\nform.status.setValue('paid'); // поле данных\nform.$.value.setValue(100); // поле, затенённое членом ноды\nform.customer.name.markAsTouched();\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","form-types","formschema","arrays","nonexistent-api","conditional-fields","array-operations","async-preload","type-safety-recipes","unified-contract","form-flow"],"sourcePath":"src/form/types/nodes.ts","entries":["."],"package":"@reformer/core","usage":34},{"name":"FormLevel","kind":"interface","signature":"export interface FormLevel<T> {\n /** Поток шагов; без `flow` в конфиге — `undefined`. */\n readonly flow: FormFlow | undefined;\n /** Идёт ли проверка: полная или шага. */\n readonly validating: ReadonlySignal<boolean>;\n /** Правила и контроллер живой проверки; без правил — `undefined`. */\n readonly validation: FormValidationState<T> | undefined;\n /**\n * Отправка: `submission.submit(handler)` — один прогон всех правил и обработчик со значениями,\n * которые они видели; `submission.submitting` — идёт ли обработчик.\n */\n readonly submission: FormSubmission<T>;\n /**\n * Проверить всю форму и показать замечания. Поля выключенных нод (`enableWhen`, `disable()`)\n * не учитываются — статус совпадает с `valid` формы.\n *\n * Правила читают модель в момент вызова. Исключение — вызов в одном тике со сборкой формы или\n * правкой поля: поведение ещё пишет отложенно (колбэк `onChange`, сброс `resetOnDisable`), и\n * проверка сначала дожидается этих записей; `validating` поднят сразу. Так же ведут себя\n * `validateStep` и отправка.\n *\n * Попыткой отправки проверка не считается: стратегия `afterFirstSubmit` остаётся в тихой фазе.\n */\n validate(): Promise<ValidationResult>;\n /**\n * Проверить шаг потока (без выключенных полей); неизвестный шаг — исключение.\n *\n * Это отчёт о шаге, как `validate` — о форме. Переход делает `flow.next()`: он сверяет модель\n * и не переходит, если её изменили, пока шла проверка шага.\n */\n validateStep(stepId: string): Promise<ValidationResult>;\n /**\n * Снять уровень формы: подписки и таймеры живой проверки; идущие проверки — полная, шага,\n * отправки — получают `cancelled`, и их замечания на ноды не ложатся. Идемпотентно. Ноды формы\n * уровень не снимает — это делает сборка (`bundle.dispose()`).\n */\n dispose(): void;\n}","summary":"Уровень формы в бандле сборки.","description":"Уровень формы в бандле сборки.","params":[],"examplesCount":0,"topics":["submit-and-reset"],"sourcePath":"src/form/form-level.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","project-structure","ui-components","nonexistent-api","conditional-fields","reading-values","array-operations","submit-and-reset","async-preload","type-safety-recipes","validation-strategy","unified-contract","form-flow"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core","usage":73},{"name":"FormNode","kind":"interface","signature":"export interface FormNode<T> {\n /** Текущее значение. */\n readonly value: ReadonlySignal<T>;\n /** Замечаний, которые блокируют отправку, нет; нода включена и не ждёт проверки. */\n readonly valid: ReadonlySignal<boolean>;\n /** Есть блокирующие замечания; нода включена и не ждёт проверки. */\n readonly invalid: ReadonlySignal<boolean>;\n /** Идёт асинхронная проверка. */\n readonly pending: ReadonlySignal<boolean>;\n /** Нода отключена: её замечания сняты, в проверку она не входит. */\n readonly disabled: ReadonlySignal<boolean>;\n /** Пользователь взаимодействовал с нодой. */\n readonly touched: ReadonlySignal<boolean>;\n /** Значение меняли после создания или сброса. */\n readonly dirty: ReadonlySignal<boolean>;\n /** Замечания ноды; у группы и массива — свои и всех детей. */\n readonly issues: ReadonlySignal<readonly ValidationIssue[]>;\n /**\n * Собственные замечания ноды — без замечаний детей: у группы и массива это правила на их ручке\n * (`minLength` массива, `cross` группы), у поля — то же, что `issues`.\n */\n readonly ownIssues: ReadonlySignal<readonly ValidationIssue[]>;\n\n /** Значение без подписки. */\n getValue(): T;\n /** Записать значение в модель. */\n setValue(value: T): void;\n /**\n * Вернуть значение и состояние к начальным; с аргументом — к переданному значению. В цель\n * `compute` значение не пишется (кроме явного `reset(значение)` самого поля): ею владеет\n * поведение — сбрасывается только состояние.\n */\n reset(value?: T): void;\n markAsTouched(): void;\n markAsUntouched(): void;\n enable(): void;\n disable(): void;\n /** Поставить замечания извне (ответ сервера, своя проверка). */\n setIssues(issues: readonly ValidationIssue[]): void;\n clearIssues(): void;\n}","summary":"Общий контракт ноды формы — поля, группы и массива под-форм.","description":"Общий контракт ноды формы — поля, группы и массива под-форм.\n\nЗначением нода не владеет: источник истины — модель (`model.$.<путь>`), нода держит состояние\nвзаимодействия (`touched`, `dirty`), доступность и замечания валидации.","params":[],"example":"```typescript\nconst { form } = createForm<{ email: string }>({ initial: { email: '' } });\nform.email.setValue('user@mail.com');\nform.email.markAsTouched();\nform.valid.value; // true — замечаний нет\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","nonexistent-api"],"sourcePath":"src/form/types/nodes.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"FormRender","kind":"interface","signature":"export interface FormRender {\n /** Скомпилированная схема корневой области; `undefined`, если схема не задана. */\n readonly tree: CompiledSchema | 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,"topics":["form-flow"],"sourcePath":"src/form/form-bundle.ts","entries":["."],"package":"@reformer/core","usage":4},{"name":"FormSchemaNode","kind":"type","signature":"export type FormSchemaNode =\n | SchemaFieldNode\n | SchemaArrayNode\n | SchemaPartNode\n | SchemaContainerNode;","summary":"Узел схемы формы: поле, массив под-форм, подформа или контейнер — см.","description":"Узел схемы формы: поле, массив под-форм, подформа или контейнер — см. описание модуля.\n\nТип закрыт: опечатка в ключе, группа без `part` и `item` не на массиве — ошибки компиляции.","params":[],"examplesCount":0,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","formschema","arrays","project-structure","ui-components","nonexistent-api","submit-and-reset","async-preload","type-safety-recipes","unified-contract","compiled-schema"],"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core","usage":46},{"name":"FormSubmission","kind":"interface","signature":"export interface FormSubmission<T> {\n /** Идёт ли отправка: работает обработчик. Пока идёт проверка, — `false`. */\n readonly submitting: ReadonlySignal<boolean>;\n /**\n * Отправить: один прогон всех правил; при успехе — обработчик со значениями модели.\n *\n * Обработчик получает ровно те значения, которые видели правила. Пока идёт async-правило,\n * форма не заперта (`submitting` ещё `false`); если модель за это время изменилась — правкой\n * пользователя, загрузкой, записью поведения после таймера или запроса, — итог `cancelled`,\n * обработчик не вызывается.\n *\n * Значения — модель целиком, с выключенными полями: их правила не считаются, но данные\n * принадлежат модели. Убрать их из отправки — забота обработчика либо\n * `enableWhen(…, { resetOnDisable: true })`.\n *\n * Исключение обработчика наружу не выходит: итог `failed` с этим исключением.\n */\n submit<TResult>(\n handler: (values: T) => TResult | Promise<TResult>\n ): Promise<SubmitResult<TResult>>;\n}","summary":"Отправка формы.","description":"Отправка формы.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","complete-import","submit-and-reset","form-flow"],"sourcePath":"src/form/submission.ts","entries":["."],"package":"@reformer/core"},{"name":"FormValidation","kind":"interface","signature":"export interface FormValidation<T> {\n /** Полный набор правил. Без него собирается из правил шагов потока и `extras`. */\n rules?: ValidationDefinition<T>;\n /** Правила, которые проверяются только целиком (отправка), но не по шагам. */\n extras?: ValidationDefinition<T>;\n /** Когда запускать живую проверку. По умолчанию `'submit'` — живой проверки нет. */\n strategy?: ValidationStrategyKind;\n debounce?: number;\n /** Режим живой фазы для `afterFirstSubmit`. По умолчанию `'change'`. */\n liveAfterSubmit?: 'change' | 'blur';\n}","summary":"Правила валидации формы и стратегия живой проверки.","description":"Правила валидации формы и стратегия живой проверки.\n\n⚠️ `rules` и `extras` обязаны быть **стабильными ссылками** (`defineValidation` в модуле): отмена\nустаревших прогонов ключится по паре «модель + правила».","params":[],"examplesCount":0,"topics":["api-reference","complete-import","project-structure","form-flow"],"sourcePath":"src/form/form-level.ts","entries":["."],"package":"@reformer/core","usage":2},{"name":"FormValidationController","kind":"interface","signature":"export interface FormValidationController {\n /**\n * Полный прогон схемы с раскрытием замечаний проверенных полей (`touch: true`). Попыткой\n * отправки не считается: `afterFirstSubmit` остаётся в тихой фазе. Возвращает `true` только для\n * статуса `valid`: ошибки, сбой правила и отмена дают `false`.\n */\n validate(): Promise<boolean>;\n /**\n * Полный прогон с полным результатом: по статусу отличают ошибки (`invalid`) от сбоя правила\n * (`error`) и отмены (`cancelled`). Без опций — тот же прогон, что\n * {@link FormValidationController.validate}; `{ submit: true }` — попытка отправки,\n * `{ touch: 'all' }` — при отказе раскрыть всю форму.\n */\n run(options?: FormValidationRunOptions): Promise<ValidationResult>;\n /**\n * Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR.\n *\n * После {@link FormValidationController.dispose} контроллер армируется ЗАНОВО: React монтирует\n * компоненты повторно (StrictMode в разработке, remount по роуту), и «одноразовый» контроллер\n * после такого цикла молча оставался бы без живой валидации.\n */\n start(): () => void;\n /**\n * Снять подписки и таймеры, отменить идущий прогон (его `validate()` или `run()` получит\n * `cancelled`). Идемпотентно; после него `start()` снова армирует стратегию.\n */\n dispose(): void;\n /** Идёт ли прогон (submit или live) — снапшот. */\n readonly isValidating: boolean;\n /** Реактивный сигнал состояния прогона (для тонкой подписки в UI). */\n readonly validating: ReadonlySignal<boolean>;\n}","summary":"","params":[],"examplesCount":0,"topics":["api-reference","validation-strategy","form-flow"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"FormValidationRunOptions","kind":"interface","signature":"export interface FormValidationRunOptions {\n /**\n * Какие поля пометить тронутыми: `true` (по умолчанию) — проверенные правилами; `'all'` — они же,\n * а при отказе (`invalid`, `error`) ещё и вся форма.\n */\n touch?: true | 'all';\n /**\n * Прогон — попытка отправки: переводит `afterFirstSubmit` в живую фазу. Проверка без отправки\n * (`validate()`, кнопка «Проверить») фазу не трогает.\n */\n submit?: boolean;\n}","summary":"Опции полного прогона {@link FormValidationController.run}.","description":"Опции полного прогона {@link FormValidationController.run}.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","validation-strategy"],"sourcePath":"src/form/validation/strategy.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"FormValidationState","kind":"interface","signature":"export interface FormValidationState<T> {\n /** Полный набор правил (стабильная ссылка). */\n readonly rules: ValidationDefinition<T>;\n readonly controller: FormValidationController;\n}","summary":"Правила формы и контроллер живой проверки — его армирует `useFormBundle`.","description":"Правила формы и контроллер живой проверки — его армирует `useFormBundle`.","params":[],"examplesCount":0,"sourcePath":"src/form/form-level.ts","entries":["."],"package":"@reformer/core"},{"name":"futureDate","kind":"function","signature":"export function futureDate(options?: RuleOptions): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора, проверяющего что дата не в прошлом.","description":"Фабрика валидатора, проверяющего что дата не в прошлом.\n\nДата не должна быть раньше сегодняшнего дня (сравнение по нормализованным датам).\nПустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для поля даты (`string | Date`)","example":"Дата не в прошлом\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, futureDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.appointmentDate, [required(), futureDate({ code: 'appointmentDate.futureDate' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/future-date.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"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":["api-reference","ui-components","nonexistent-api"],"sourcePath":"src/platforms/react/field-binding/field-adapter.ts","entries":["./react"],"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)?.setIssues([{ code: 'required' }]);\n```","examplesCount":2,"related":["registerSignalNode"],"topics":["api-reference","array-operations"],"sourcePath":"src/form/signal-node-registry.ts","entries":["."],"package":"@reformer/core","usage":5},{"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":"hideWhen","kind":"function","signature":"export function hideWhen(node: SchemaNodeControl, condition: () => boolean): void","summary":"Скрывать узел, пока условие истинно.","description":"Скрывать узел, пока условие истинно.\n\nУсловие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.\nПринудительное `schema.node(selector).setHidden(...)` главнее условия.\n\nТолько видимость: поля скрытого узла не выключаются и проверяются правилами — переход шага\nоткажет без замечаний на экране. Поля узла выключай тем же условием через `enableWhen`.","params":[],"example":"```typescript\nconst mortgage = () => model.loanType === 'mortgage';\nenableWhen([model.$.propertyValue, model.$.initialPayment], mortgage, { resetOnDisable: true });\nhideWhen(schema.node('mortgage'), () => !mortgage());\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","project-structure","conditional-fields","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":["./react"],"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 /**\n * Повторить загрузку языка, которая упала (`error` задан). Без загрузчика и без ошибки ничего\n * не делает.\n */\n readonly retry: () => void;\n}","summary":"Что видит компонент: ручка активной локали и состояние её загрузки.","description":"Что видит компонент: ручка активной локали и состояние её загрузки.","params":[],"examplesCount":0,"sourcePath":"src/platforms/react/i18n/context.ts","entries":["./react"],"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,"topics":["issue-messages"],"sourcePath":"src/i18n/translator.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"I18nLoadingProviderProps","kind":"interface","signature":"export interface I18nLoadingProviderProps {\n /** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */\n lang: string;\n /**\n * Загрузчик локалей — обычно результат `createLocaleLoader`. Нужна СТАБИЛЬНАЯ ссылка (модульная\n * константа, `useMemo`): новый загрузчик — новое хранилище, и язык запрашивается у него заново.\n * На экране тем временем остаётся прежняя локаль, но инлайн-стрелка гоняла бы запрос на каждый\n * рендер родителя.\n */\n load: LocaleLoad;\n /**\n * Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр\n * мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`\n * до монтирования), первый рендер синхронный и `fallback` не показывается. Упала первая\n * загрузка — дети показываются на встроенном английском, ошибка — в `useI18n().error`,\n * повторить — `useI18n().retry()`.\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":["./react"],"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":["api-reference","common-mistakes","troubleshooting","complete-import","nonexistent-api","i18n","issue-messages"],"sourcePath":"src/platforms/react/i18n/provider.ts","entries":["./react"],"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":["./react"],"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":["./react"],"package":"@reformer/core"},{"name":"integer","kind":"function","signature":"export function integer(options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора, проверяющего что число — целое.","description":"Фабрика валидатора, проверяющего что число — целое.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для числового поля","example":"Проверка целого числа\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, integer } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.count, [required(), integer({ code: 'count.integer' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/integer.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"isCompiledNode","kind":"function","signature":"export function isCompiledNode(value: unknown): value is CompiledNode","summary":"Узел скомпилированной схемы, а не авторский узел?","description":"Узел скомпилированной схемы, а не авторский узел?","params":[["value","- Что угодно."]],"returns":"`true` для узла, выданного {@link compileSchema}.","example":"```typescript\nisCompiledNode(compileSchema(tree, model).root); // true\nisCompiledNode(tree); // false\n```","examplesCount":1,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compile.ts","entries":["."],"package":"@reformer/core"},{"name":"isDate","kind":"function","signature":"export function isDate(options?: RuleOptions): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора, проверяющего что значение — валидная дата.","description":"Фабрика валидатора, проверяющего что значение — валидная дата.\n\nСтрогий формат: `Date` либо строка `YYYY-MM-DD` с существующим днём календаря. Строки в иных\nформатах (`'31.12.2024'`, `'March 7'`, дата со временем) — `date_invalid`. Пустые значения\n(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для поля даты (`string | Date`)","example":"Проверка валидности даты\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, isDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.eventDate, [required(), isDate({ code: 'eventDate.isDate' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/is-date.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"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":"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":"isFormArray","kind":"function","signature":"export function isFormArray(value: unknown): value is FormArray<object>","summary":"Массив под-форм.","description":"Массив под-форм.","params":[],"example":"```typescript\nif (isFormArray(node)) node.push();\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","formschema","nonexistent-api","type-safety-recipes"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isFormControl","kind":"function","signature":"export function isFormControl(value: unknown): value is FormControl<unknown>","summary":"Поле формы — нода над листом модели с пропсами компонента.","description":"Поле формы — нода над листом модели с пропсами компонента.","params":[],"example":"```typescript\nif (isFormControl(node)) node.updateComponentProps({ placeholder: 'Email' });\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","formschema","nonexistent-api","type-safety-recipes"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isFormGroup","kind":"function","signature":"export function isFormGroup(value: unknown): value is FormGroup<Record<string, unknown>>","summary":"Группа полей — её прокси с нодами полей по именам.","description":"Группа полей — её прокси с нодами полей по именам.","params":[],"example":"```typescript\nif (isFormGroup(node)) node.markAsTouched();\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","formschema","nonexistent-api","type-safety-recipes"],"sourcePath":"src/form/type-guards.ts","entries":["."],"package":"@reformer/core"},{"name":"isFormNode","kind":"function","signature":"export function isFormNode(value: unknown): value is FormNode<unknown>","summary":"Нода формы любого вида — поле, группа или массив под-форм.","description":"Нода формы любого вида — поле, группа или массив под-форм.","params":[],"example":"```typescript\nconst node = getNodeForSignal(model.$.email);\nif (isFormNode(node)) node.markAsTouched();\n```","examplesCount":1,"topics":["api-reference","common-mistakes","nonexistent-api"],"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(options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора, проверяющего что значение — конечное число (не `NaN`, не `±Infinity`, не строка).","description":"Фабрика валидатора, проверяющего что значение — конечное число (не `NaN`, не `±Infinity`,\nне строка).\n\nПустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required} для\nобязательности). В отличие от других number-валидаторов, **не** пропускает не-числа\nи `NaN` — это его задача.","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для числового поля","example":"Проверка, что значение — число\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, isNumber } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.amount, [required(), isNumber({ code: 'amount.isNumber' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/is-number.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"isSignal","kind":"function","signature":"export function isSignal(value: unknown): value is ReadonlySignal<unknown>","summary":"Значение — сигнал: результат `signal()` или `computed()`, в том числе созданный другой копией рантайма.","description":"Значение — сигнал: результат `signal()` или `computed()`, в том числе созданный другой копией\nрантайма.\n\nКонтейнерные узлы дерева `model.$` (группа, массив) тоже проходят проверку: это сигналы\nагрегированного значения. Лист от контейнера отличает `isModelContainerSignal`, а ручку, к\nкоторой можно привязать поле, — `isValueSignal` (оба — из `@reformer/core`).","params":[],"example":"```typescript\nisSignal(signal(1)); // true\nisSignal(computed(() => 1)); // true\nisSignal({ value: 1 }); // false\n```","examplesCount":1,"topics":["nonexistent-api"],"sourcePath":"src/signals.ts","entries":["./signals"],"package":"@reformer/core"},{"name":"IssueMessage","kind":"type","signature":"export type IssueMessage =\n | string\n | ((params: Readonly<Record<string, unknown>>, i18n: I18nHandle) => string);","summary":"Текст замечания в словаре формы: шаблон с параметрами либо функция.","description":"Текст замечания в словаре формы: шаблон с параметрами либо функция.\n\nШаблон — тот же формат сообщений, что в словарях локали: `{minLength}`, `plural`, `select`.\nПараметры в него приходят подготовленными: дата — по языку, массив — перечнем, размер файла —\nстрокой с единицей. Число шаблон печатает как есть, без разрядов: «50 000 ₽» пишут готовым\nтекстом либо функцией, которая форматирует число сама.","params":[],"example":"```ts\nconst byTemplate: IssueMessage = 'Не меньше {minLength} символов';\nconst byFunction: IssueMessage = (params, i18n) => `Не меньше ${i18n.number(Number(params.min))} ₽`;\n```","examplesCount":1,"topics":["issue-messages"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"IssueMessages","kind":"type","signature":"export type IssueMessages = Readonly<Record<string, IssueMessage>>;","summary":"Словарь формы: код замечания → текст.","description":"Словарь формы: код замечания → текст. Ключ — `code` замечания: встроенный (`required`) либо\nсвой (`loanAmount.required`).","params":[],"examplesCount":0,"topics":["issue-messages"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"package":"@reformer/core"},{"name":"isValueSignal","kind":"function","signature":"export function isValueSignal(value: unknown): value is Signal<unknown>","summary":"«Ручка значения», к которой можно привязать поле: лист (сигнал, не контейнер) либо узел-массив дерева `model.$` (значение — массив целиком, `.value` читается и пишется).","description":"«Ручка значения», к которой можно привязать поле: лист (сигнал, не контейнер) либо узел-массив\nдерева `model.$` (значение — массив целиком, `.value` читается и пишется).\n\nГруппа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой\n`createForm` и рендереры отличают узел-поле схемы (`{ model, component }`) от контейнера.","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,"topics":["nonexistent-api"],"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 /** Повторить запрос языка, загрузка которого упала. Без ошибки — no-op. */\n retry(): 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 /**\n * Локаль, которая уже на экране (прежнее хранилище провайдера с другим загрузчиком). Пока новый\n * загрузчик не отдал язык, на экране остаётся она, а не «ничего не загружено».\n */\n readonly previous?: FormLocale;\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":["./react"],"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`, `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"],"topics":["compute-vs-watch","submit-and-reset"],"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\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(maxValue: number, options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора максимального числового значения.","description":"Фабрика валидатора максимального числового значения.\n\nПустые значения и не-числа (`''`, строка из цифр, `NaN`) пропускаются — используйте\n{@link required} и {@link isNumber}.","params":[["maxValue","- Максимально допустимое значение (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `max` и `actual`."]],"returns":"Правило {@link Rule} для числового поля","example":"Максимальное значение числового поля\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, max } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.quantity, [max(100)]);\nvalidate(model.$.discount, [required(), max(50, { code: 'discount.max' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","form-types","revalidate-when","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/max.ts","entries":["./validators"],"package":"@reformer/core","usage":41},{"name":"maxAge","kind":"function","signature":"export function maxAge(\n maxAgeValue: number,\n options?: RuleOptions\n): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора максимального возраста (по дате рождения).","description":"Фабрика валидатора максимального возраста (по дате рождения).\n\nВозраст вычисляется по дате рождения относительно сегодняшнего дня; родившийся 29 февраля в\nневисокосный год взрослеет 28 февраля. Дату рождения в будущем правило не оценивает — её\nотклоняет {@link minAge} (или {@link pastDate}); иначе пара правил на поле дала бы два\nодинаковых замечания. Пустые и невалидные даты пропускаются (используйте {@link required} и\n{@link isDate}).","params":[["maxAgeValue","- Максимально допустимый возраст (в полных годах)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `maxAge` и `currentAge`."]],"returns":"Правило {@link Rule} для поля даты рождения (`string | Date`)","example":"Максимальный возраст\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxAge } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [maxAge(100, { code: 'birthDate.maxAge' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/max-age.ts","entries":["./validators"],"package":"@reformer/core","usage":13},{"name":"maxDate","kind":"function","signature":"export function maxDate(\n maxDateValue: Date,\n options?: RuleOptions\n): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора максимальной даты (включительно).","description":"Фабрика валидатора максимальной даты (включительно).\n\nСравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты\nпропускаются (используйте {@link required} и {@link isDate}).","params":[["maxDateValue","- Максимально допустимая дата (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадает `maxDate`."]],"returns":"Правило {@link Rule} для поля даты (`string | Date`)","example":"Максимальная дата\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [maxDate(new Date(), { code: 'birthDate.maxDate' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/max-date.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"maxFiles","kind":"function","signature":"export function maxFiles(max: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора максимального количества файлов.","description":"Фабрика валидатора максимального количества файлов.\n\nРаботает с массивом файлов (одиночный файл считается как 1). Пустые значения\n(`null`/`undefined`/`''`/`[]`) пропускаются (используйте {@link required}\nдля обязательности).","params":[["max","- Максимально допустимое количество файлов (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `maxFiles` и `actualCount`."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Не более трёх вложений\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxFiles } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxFiles(3, { code: 'documents.maxFiles' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","issue-messages"],"sourcePath":"src/form/validators/max-files.ts","entries":["./validators"],"package":"@reformer/core","usage":14},{"name":"maxFileSize","kind":"function","signature":"export function maxFileSize(maxSize: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора максимального размера файла.","description":"Фабрика валидатора максимального размера файла.\n\nРаботает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с\n`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.\nПустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`\n(например, дескриптор загруженного файла без размера) пропускаются\n(используйте {@link required} для обязательности).","params":[["maxSize","- Максимально допустимый размер файла в байтах (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `maxFileSize`, `fileName` и `actualSize`."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Ограничение размера вложений\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { code: 'documents.maxFileSize' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","i18n","issue-messages"],"sourcePath":"src/form/validators/max-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":10},{"name":"maxLength","kind":"function","signature":"export function maxLength(maxLen: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора максимальной длины строки или массива.","description":"Фабрика валидатора максимальной длины строки или массива.\n\nРаботает со строкой или массивом (проверяется `value.length`). Пустые значения\n(`null`/`undefined`/`''`) и значения без числового `length` пропускаются\n(используйте {@link required} для обязательности).\n\nДлина строки — в единицах UTF-16, как `String.length` и атрибут `maxlength` браузера: эмодзи и\nдругие символы вне BMP считаются за 2, графемы не склеиваются.\n\nУ строки замечание — `maxLength` (символы), у массива — `maxItems` (элементы): мультивыбор и\nсписок считают выбранное, и текст «не больше 3 символов» под ними был бы бессмыслицей.","params":[["maxLen","- Максимально допустимая длина (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `maxLength` и `actualLength`."]],"returns":"Правило {@link Rule} для строки или массива","example":"Максимальная длина строки\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxLength } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.name, [maxLength(50)]);\nvalidate(model.$.bio, [maxLength(500, { code: 'bio.maxLength' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","arrays","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/max-length.ts","entries":["./validators"],"package":"@reformer/core","usage":80},{"name":"maxTotalFileSize","kind":"function","signature":"export function maxTotalFileSize(maxTotal: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора суммарного размера всех файлов.","description":"Фабрика валидатора суммарного размера всех файлов.\n\nСкладывает `size` всех файлов значения (элементы без числового `size` в сумму\nне входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются\n(используйте {@link required} для обязательности).","params":[["maxTotal","- Максимально допустимый суммарный размер в байтах (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `maxTotalFileSize` и `actualTotal`."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Суммарно не более 20 МБ\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { maxTotalFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { code: 'documents.maxTotalFileSize' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","i18n","issue-messages"],"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(minValue: number, options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора минимального числового значения.","description":"Фабрика валидатора минимального числового значения.\n\nПустые значения и не-числа (`''`, строка из цифр, `NaN`) пропускаются — используйте\n{@link required} и {@link isNumber}.","params":[["minValue","- Минимально допустимое значение (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `min` и `actual`."]],"returns":"Правило {@link Rule} для числового поля","example":"Минимальное значение числового поля\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, min } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.age, [min(18)]);\nvalidate(model.$.quantity, [required(), min(1, { code: 'quantity.min' })]);\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","i18n","issue-messages"],"sourcePath":"src/form/validators/min.ts","entries":["./validators"],"package":"@reformer/core","usage":73},{"name":"minAge","kind":"function","signature":"export function minAge(\n minAgeValue: number,\n options?: RuleOptions\n): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора минимального возраста (по дате рождения).","description":"Фабрика валидатора минимального возраста (по дате рождения).\n\nВозраст вычисляется по дате рождения относительно сегодняшнего дня; родившийся 29 февраля в\nневисокосный год взрослеет 28 февраля. Дата рождения в будущем — замечание `date_future` с\n`params.minAge` (свой `code` из опций его заменяет), а не возраст −1. Пустые и невалидные даты пропускаются\n(используйте {@link required} и {@link isDate}).","params":[["minAgeValue","- Минимально допустимый возраст (в полных годах)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `minAge` и `currentAge`."]],"returns":"Правило {@link Rule} для поля даты рождения (`string | Date`)","example":"Минимальный возраст\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, minAge } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [required(), minAge(18, { code: 'birthDate.minAge' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","form-types","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/min-age.ts","entries":["./validators"],"package":"@reformer/core","usage":14},{"name":"minDate","kind":"function","signature":"export function minDate(\n minDateValue: Date,\n options?: RuleOptions\n): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора минимальной даты (включительно).","description":"Фабрика валидатора минимальной даты (включительно).\n\nСравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты\nпропускаются (используйте {@link required} и {@link isDate}).","params":[["minDateValue","- Минимально допустимая дата (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадает `minDate`."]],"returns":"Правило {@link Rule} для поля даты (`string | Date`)","example":"Минимальная дата\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, minDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.startDate, [required(), minDate(new Date(), { code: 'startDate.minDate' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","form-types","type-safety-recipes","issue-messages"],"sourcePath":"src/form/validators/min-date.ts","entries":["./validators"],"package":"@reformer/core","usage":6},{"name":"minFiles","kind":"function","signature":"export function minFiles(min: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора минимального количества файлов.","description":"Фабрика валидатора минимального количества файлов.\n\nРаботает с массивом файлов (одиночный файл считается как 1). Пустые значения\n(`null`/`undefined`/`''`) пропускаются (используйте {@link required}), а пустой массив — как\nу {@link minLength} — проверяется: при `min > 0` это замечание с `actualCount: 0`. Элементы\nмассива, которые не файлы, не считаются.","params":[["min","- Минимально допустимое количество файлов (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `minFiles` и `actualCount`."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Не менее двух документов\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, minFiles } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [required(), minFiles(2, { code: 'documents.minFiles' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","issue-messages"],"sourcePath":"src/form/validators/min-files.ts","entries":["./validators"],"package":"@reformer/core","usage":4},{"name":"minFileSize","kind":"function","signature":"export function minFileSize(minSize: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора минимального размера файла.","description":"Фабрика валидатора минимального размера файла.\n\nРаботает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с\n`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.\nТиповой сценарий — отсев пустых (0-байтовых) файлов: `minFileSize(1)`.\nПустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`\nпропускаются (используйте {@link required} для обязательности).","params":[["minSize","- Минимально допустимый размер файла в байтах (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `minFileSize`, `fileName` и `actualSize`."]],"returns":"Правило {@link Rule} для файла или массива файлов","example":"Отсев пустых файлов\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { minFileSize } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.documents, [minFileSize(1, { code: 'documents.minFileSize' })]);\n});\n```","examplesCount":1,"topics":["api-signatures","i18n","issue-messages"],"sourcePath":"src/form/validators/min-file-size.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"minLength","kind":"function","signature":"export function minLength(minLen: number, options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора минимальной длины строки или массива.","description":"Фабрика валидатора минимальной длины строки или массива.\n\nРаботает со строкой или массивом (проверяется `value.length`). Пустые значения\n(`null`/`undefined`/`''`) и значения без числового `length` пропускаются\n(используйте {@link required} для обязательности).\n\nДлина строки — в единицах UTF-16, как `String.length` и атрибут `minlength` браузера: эмодзи и\nдругие символы вне BMP считаются за 2, графемы не склеиваются.\n\nУ строки замечание — `minLength` (символы), у массива — `minItems` (элементы): мультивыбор и\nсписок считают выбранное, и текст «не меньше 3 символов» под ними был бы бессмыслицей.","params":[["minLen","- Минимально допустимая длина (включительно)"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадают `minLength` и `actualLength`."]],"returns":"Правило {@link Rule} для строки или массива","example":"Минимальная длина строки\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, minLength } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.name, [minLength(2)]);\nvalidate(model.$.password, [required(), minLength(8, { code: 'password.minLength' })]);\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","issue-messages"],"sourcePath":"src/form/validators/min-length.ts","entries":["./validators"],"package":"@reformer/core","usage":86},{"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 * Если в объекте не хватает ключей (значение пришло без типа — например, с сервера), эти поля\n * остаются прежними, а в dev выводится предупреждение со списком.\n */\n set(value: T): void;\n /**\n * Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,\n * отсутствующие ключи НЕ трогаются. Вложенные группы сливаются так же, массив заменяется\n * целиком. Не меняет initial-снимок.\n */\n patch(value: DeepPartial<T>): void;\n /**\n * Отличаются ли текущие значения от initial-снимка (value-diff). Производные поля — цели\n * `compute` / `computeFrom` и поля с `markDerived` — не в счёт: их пишет поведение, а не\n * пользователь, и форма без единой правки изменённой не считается.\n */\n isDirty(): boolean;\n /**\n * Сбросить значения к initial-снимку. Производные поля сброс не пишет, как `set` и `patch`:\n * цель `compute` пересчитает поведение, когда вернутся её источники.\n *\n * Ноды формы не трогает: замечания погаснут при следующей проверке. Сброс для пользователя —\n * `form.reset()`: та же точка отсчёта и чистые ноды.\n */\n reset(): void;\n /**\n * Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). К нему возвращают\n * и `model.reset()`, и `form.reset()`.\n */\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 /** Обход `for…of` / spread — те же элементы, что у {@link forEach}. */\n [Symbol.iterator](): IterableIterator<ModelArrayItem<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":"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} — к нему привязывают поле (`{ model: model.$.tags, component }`),\nправило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением\nполя: мультивыбор, теги, список файлов.\n\n`V` — объявленный тип поля, включая `undefined`. Поле с `null` в типе (`string[] | null`) — не\nузел-массив, а лист {@link PathAwareSignal}: его начинают с `null`, и `length` с индексами у него\nв рантайме нет (см. {@link ModelValue}).","params":[],"examplesCount":0,"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"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":["api-reference","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"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"ModelValue","kind":"type","signature":"export type ModelValue<V> =\n IncludesNull<V> extends true\n ? 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- примитив → значение\n- тип с `null` (`File[] | null`, `Address | null`) → значение как есть, без фасада: поле,\n которое бывает `null`, начинают с `null`, а такое поле в рантайме — лист (значение целиком).\n Контейнер, созданный из `[]` или `{}`, стал бы фасадом — nullable-контейнер начинают с `null`.","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{ model: 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(\n divisor: number,\n options?: RuleOptions\n): Rule<number | null | undefined>","summary":"Фабрика валидатора, проверяющего что число кратно заданному.","description":"Фабрика валидатора, проверяющего что число кратно заданному.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["divisor","- Делитель: значение должно быть кратно ему. Конечное число, не равное нулю, —\nиначе ошибка конфигурации (`RangeError`) при создании правила: кратность нулю вырождена."],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадает `multipleOf` (делитель)."]],"returns":"Правило {@link Rule} для числового поля","example":"Проверка кратности\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { multipleOf } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.rating, [multipleOf(0.5, { code: 'rating.multipleOf' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/multiple-of.ts","entries":["./validators"],"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(options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).","description":"Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для числового поля","example":"Проверка неотрицательности\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { nonNegative } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.balance, [nonNegative({ code: 'balance.nonNegative' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/non-negative.ts","entries":["./validators"],"package":"@reformer/core","usage":2},{"name":"nonZero","kind":"function","signature":"export function nonZero(options?: RuleOptions): Rule<number | null | undefined>","summary":"Фабрика валидатора, проверяющего что число не равно нулю.","description":"Фабрика валидатора, проверяющего что число не равно нулю.\n\nПустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для числового поля","example":"Проверка «не ноль»\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { nonZero } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.divisor, [nonZero({ code: 'divisor.nonZero' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"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` перед применением результата — это убирает гонки устаревших ответов.","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","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","async-options-loading","i18n","form-flow"],"sourcePath":"src/form/behaviors/operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":27},{"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`. У поля и массива событие уже занято привязкой (запись\nзначения в форму, кнопки массива) — обработчик вызывается после неё, а не вместо: ввод не ломается.\nУ подформы своего компонента нет — правило не действует, в dev рендерер предупреждает.","params":[],"example":"```typescript\nonComponentEvent(schema.node('wizard'), 'onSubmit', async () => {\n await submitApplication(model.get());\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","project-structure","unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":10},{"name":"onDispose","kind":"function","signature":"export function onDispose(cleanup: BehaviorCleanup): void","summary":"Зарегистрировать произвольную отписку в активной схеме.","description":"Зарегистрировать произвольную отписку в активной схеме.","params":[],"examplesCount":0,"topics":["api-reference","unified-contract"],"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":["api-reference","api-signatures","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":["api-reference","api-signatures","unified-contract"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":4},{"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":["api-reference","api-signatures","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(options?: RuleOptions): Rule<string | Date | null | undefined>","summary":"Фабрика валидатора, проверяющего что дата не в будущем.","description":"Фабрика валидатора, проверяющего что дата не в будущем.\n\nДата не должна быть позже сегодняшнего дня (сравнение по нормализованным датам).\nПустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для поля даты (`string | Date`)","example":"Дата не в будущем\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, pastDate } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.birthDate, [required(), pastDate({ code: 'birthDate.pastDate' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","issue-messages"],"sourcePath":"src/form/validators/past-date.ts","entries":["./validators"],"package":"@reformer/core","usage":9},{"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","arrays","nonexistent-api","reading-values"],"sourcePath":"src/model/types.ts","entries":[".","./model"],"package":"@reformer/core"},{"name":"pattern","kind":"function","signature":"export function pattern(regex: RegExp, options?: RuleOptions): Rule<string | null | undefined>","summary":"Фабрика валидатора регулярного выражения.","description":"Фабрика валидатора регулярного выражения.\n\nПустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}\nдля обязательности).","params":[["regex","- Регулярное выражение для проверки значения"],["options","- Опции валидатора ({@link RuleOptions}). В `params` ошибки автоматически\nпопадает `pattern` (строка-источник regex)."]],"returns":"Правило {@link Rule} для строкового поля","example":"Проверка по регулярному выражению\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, pattern } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.name, [pattern(/^[a-zA-Zа-яА-Я]+$/, { code: 'name.pattern' })]);\nvalidate(model.$.phone, [required(), pattern(/^\\+7 \\(\\d{3}\\) \\d{3}-\\d{2}-\\d{2}$/, { code: 'phone.pattern' })]);\n});\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","complete-import","nonexistent-api","copy-from","async-validator-debounce","issue-messages"],"sourcePath":"src/form/validators/pattern.ts","entries":["./validators"],"package":"@reformer/core","usage":57},{"name":"phone","kind":"function","signature":"export function phone(options?: PhoneValidatorOptions): Rule<string | null | undefined>","summary":"Фабрика валидатора номера телефона.","description":"Фабрика валидатора номера телефона.\n\nПроверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения\n(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).","params":[["options","- Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки\nавтоматически попадает выбранный `format`."]],"returns":"Правило {@link Rule} для строкового поля","example":"Проверка номера телефона\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, phone } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.phone, [required(), phone({ format: 'ru' })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","troubleshooting","complete-import","form-types","ui-components","nonexistent-api","array-operations","transform-value","i18n","issue-messages"],"sourcePath":"src/form/validators/phone.ts","entries":["./validators"],"package":"@reformer/core","usage":34},{"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 RuleOptions {\n /** Формат проверки {@link PhoneFormat}. По умолчанию `'any'`. */\n format?: PhoneFormat;\n}","summary":"Опции валидатора {@link phone}.","description":"Опции валидатора {@link phone}. Расширяют {@link RuleOptions} (`code`, `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группы и массива под-форм; у группы в реестре лежит её лицо {@link FormGroup} с нодами полей.\nПрикладной код обычно этот реестр не заполняет напрямую.","params":[["signal","- Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`"],["node","- Нода формы, отвечающая за эту часть модели"]],"example":"Ручка модели указывает на ноду другой сборки\n```typescript\nimport { registerSignalNode } from '@reformer/core';\n\nregisterSignalNode(model.$.profile.email, profileForm.email);\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 panel = schema.node('summary').getRef<HTMLElement>();\nrenderEffect(schema, () => {\n if (model.loanType === 'mortgage') panel.current?.scrollIntoView();\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","unified-contract","form-flow"],"sourcePath":"src/form/behaviors/schema-operators.ts","entries":["./behaviors"],"package":"@reformer/core","usage":3},{"name":"required","kind":"function","signature":"export function required(options?: RuleOptions): Rule<unknown>","summary":"Фабрика валидатора обязательного поля.","description":"Фабрика валидатора обязательного поля.\n\nВозвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.\n\nПустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —\nобязательный multi-select / FieldArray без выбранных элементов), `NaN` (так выглядит очищенный\nчисловой ввод).\nДля boolean полей требуется значение `true`.","params":[["options","- Опции валидатора ({@link RuleOptions}): `code`, `params`"]],"returns":"Правило {@link Rule} для поля схемы","example":"Обязательные поля в схеме формы\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.email, [required()]);\nvalidate(model.$.phone, [required({ code: 'phone.required' })]);\nvalidate(model.$.agreeToTerms, [required({ code: 'agreeToTerms.required' })]);\n});\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","reset-when","submit-and-reset","type-safety-recipes","async-validator-debounce","validation-strategy","i18n","unified-contract","issue-messages"],"sourcePath":"src/form/validators/required.ts","entries":["./validators"],"package":"@reformer/core","usage":331},{"name":"resetWhen","kind":"function","signature":"export function resetWhen<T>(\n target: Signal<T>,\n condition: () => boolean,\n ...[options]: ResetWhenOptionsArg<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","common-mistakes","complete-import","nonexistent-api","conditional-fields","cycle-detection","copy-from","reset-when"],"sourcePath":"src/model/behaviors-value.ts","entries":["./model","./behaviors"],"package":"@reformer/core","usage":4},{"name":"ResetWhenOptionsArg","kind":"type","signature":"export type ResetWhenOptionsArg<T> = null extends T\n ? [options?: { resetValue?: NoInfer<T> }]\n : [options: { resetValue: NoInfer<T> }];","summary":"Опции {@link resetWhen}.","description":"Опции {@link resetWhen}. Без `resetValue` поле сбрасывается в `null`, поэтому `resetValue`\nобязателен, если `null` не входит в тип поля: `resetWhen(model.$.promoCode, cond)` для поля\n`string` не компилируется — нужно `{ resetValue: '' }`.","params":[],"examplesCount":0,"topics":["reset-when"],"sourcePath":"src/model/behaviors-value.ts","entries":["./model"],"package":"@reformer/core"},{"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":"resolveIssueMessage","kind":"function","signature":"export function resolveIssueMessage(\n issue: ValidationIssue,\n i18n: I18nHandle,\n messages?: IssueMessages\n): string","summary":"Отображаемый текст замечания валидации.","description":"Отображаемый текст замечания валидации. Порядок источников — в шапке модуля.","params":[["issue","- Замечание от правила: `code` и `params`."],["i18n","- Ручка активной локали (`createI18n` / `useI18n`)."],["messages","- Словарь формы; его записи важнее словаря локали."]],"example":"```ts\nresolveIssueMessage({ code: 'minLength', params: { minLength: 8 } }, i18n);\n// 'Enter at least 8 characters' · в русской локали — 'Не меньше 8 символов'\n\nresolveIssueMessage({ code: 'phone.required' }, i18n, { 'phone.required': 'Укажите телефон' });\n// 'Укажите телефон' — текст поля из словаря формы\n```","examplesCount":1,"topics":["api-reference","common-mistakes","complete-import","nonexistent-api","i18n","issue-messages"],"sourcePath":"src/i18n/validation-message.ts","entries":["./i18n"],"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":"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","common-mistakes","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) => ValidationIssue | null;","summary":"Синхронное правило поля типа `TField`: получает значение, возвращает ошибку или `null`.","description":"Синхронное правило поля типа `TField`: получает значение, возвращает ошибку или `null`.\n\nПравило, написанное для более широкого типа, подходит полю с более узким: `min()` принимает\n`number | null | undefined` и годится и полю `number`, и полю `number | null`; `required()`\nпринимает любое значение. Обратное — ошибка компиляции: `validate(model.$.age, [email()])`\nподсветится, потому что `email` ждёт строку, а поле числовое.\n\nПравило над несколькими полями — оператор `cross` области схемы: он получает снимок модели.","params":[],"example":"```ts\nconst AMOUNT_RULES: Rule<number | null>[] = [required(), min(50000)];\nconst notReserved: Rule<string> = (value) =>\n value === 'admin' ? { code: 'reserved' } : null;\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","project-structure","nonexistent-api","conditional-fields","compute-vs-watch","type-safety-recipes","unified-contract"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core","usage":91},{"name":"RuleOptions","kind":"interface","signature":"export interface RuleOptions {\n /**\n * Свой код вместо встроенного. По нему текст ищется сначала в словаре формы, затем в локали\n * (`validation.<code>`); принято именовать `<поле>.<правило>`.\n */\n code?: string;\n /** Дополнительные параметры замечания: подставляются в текст поверх параметров правила. */\n params?: Readonly<Record<string, unknown>>;\n}","summary":"Опции валидатора-фабрики (`required()`/`pattern()`/…).","description":"Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)\nаргументом в фабрику и попадают в возвращаемое {@link ValidationIssue}.\n\nТекста среди опций нет: правило сообщает только код и параметры, а строку по коду даёт слой\nпредставления (`resolveIssueMessage` из `@reformer/core/i18n`). Нужен свой текст для\nконкретного поля — дайте правилу свой код и запись в словаре формы.","params":[],"example":"```ts\n// правило\nrequired({ code: 'loanAmount.required' });\n// словарь формы (`defineIssueMessages`)\nconst messages = defineIssueMessages({ 'loanAmount.required': 'Укажите сумму кредита' });\n```","examplesCount":1,"topics":["api-reference","api-signatures","troubleshooting","complete-import","nonexistent-api","i18n","issue-messages"],"sourcePath":"src/form/types/validation-schema.ts","entries":["."],"package":"@reformer/core"},{"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":"runValidation","kind":"function","signature":"export async function runValidation<T>(\n model: FormModel<T>,\n schema: ValidationDefinition<T>\n): Promise<ValidationResult>","summary":"Собрать результат проверки модели схемой — без формы и без побочных эффектов на нодах.","description":"Собрать результат проверки модели схемой — без формы и без побочных эффектов на нодах.\n\nОткрывает ambient-окно на время синхронного прогона `schema`, дожидается async-правил и\nвозвращает {@link ValidationResult}. Нод формы не трогает: ошибки по ним разносит\n{@link applyValidationResult}. Подходит для проверки данных без UI (сервер, тест, предпросмотр).\n\nСтатус результата:\n - `cancelled` — прогон устарел: пока он шёл, для той же пары (model, schema) запущен следующий;\n - `error` — хотя бы одно async-правило не вернуло результат (сеть, исключение) — см. `failures`;\n - `invalid` — есть блокирующие ошибки (`severity: 'warning'` не блокирует);\n - `valid` — блокирующих ошибок нет.\n\n⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности\n`schema`. Держите схемы в `const` / `defineValidation`.","params":[["model","- Модель данных формы."],["schema","- Схема валидации (стабильная ссылка)."]],"example":"```ts\nconst result = await runValidation(model, formRules);\nif (result.status === 'error') showToast('Не удалось проверить форму, повторите попытку');\nelse if (result.status !== 'cancelled') applyValidationResult(result, { touch: true });\n```","examplesCount":1,"topics":["api-reference","api-signatures","complete-import","nonexistent-api","submit-and-reset","async-validator-debounce","unified-contract","form-flow"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"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)"]],"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":"Контракт реактивного массива модели — фасада `model.<массив>`.","description":"Контракт реактивного массива модели — фасада `model.<массив>`. Рендерерский\n`RenderModelArrayControl` — его расширение (добавляет `move`).","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaArrayHandle","kind":"type","signature":"export type SchemaArrayHandle = SchemaValueHandle & { readonly length: number };","summary":"Ручка массива из дерева `model.$` (`model.$.phones`) — ручка значения с длиной.","description":"Ручка массива из дерева `model.$` (`model.$.phones`) — ручка значения с длиной.","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaArrayNode","kind":"interface","signature":"export interface SchemaArrayNode extends SchemaNodeBase {\n /** Привязка — ручка массива `model.$.<массив>`. */\n model: SchemaArrayHandle;\n /** Схема строки: под-модель строки → узел поддерева. */\n item: (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 part?: never;\n children?: never;\n}","summary":"Массив под-форм: у каждой строки своя форма, разметку строки строит `item`.","description":"Массив под-форм: у каждой строки своя форма, разметку строки строит `item`.","params":[],"example":"```ts\n{ model: model.$.phones, component: FieldArray, item: (row) => ({ children: [...] }) }\n```","examplesCount":1,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaChild","kind":"type","signature":"export type SchemaChild = FormSchemaNode | string | number | Signal<any>;","summary":"Ребёнок контейнера: вложенный узел либо текстовая часть — литерал, число, сигнал модели.","description":"Ребёнок контейнера: вложенный узел либо текстовая часть — литерал, число, сигнал модели.","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaContainerNode","kind":"interface","signature":"export interface SchemaContainerNode extends SchemaNodeBase {\n children?: readonly SchemaChild[];\n model?: never;\n item?: never;\n part?: never;\n}","summary":"Контейнер: узел с детьми (секция, шаг визарда, html-тег).","description":"Контейнер: узел с детьми (секция, шаг визарда, html-тег). Текст — такой же ребёнок, как узел:\nядро его не интерпретирует, а рендерер выводит на своём месте в порядке следования.","params":[],"example":"```ts\n{ component: Section, componentProps: { title: 'Контакты' }, children: [phoneField, emailField] }\n```","examplesCount":1,"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":"SchemaFieldNode","kind":"interface","signature":"export interface SchemaFieldNode extends SchemaNodeBase {\n /** Привязка поля — ручка значения `model.$.<лист | массив>`. */\n model: SchemaValueHandle;\n /**\n * Поле создаётся отключённым — и остаётся: это постоянная блокировка (вычисляемые `total`,\n * `fullName`). `enable()` группы, массива или формы её не снимает; снимает только явный\n * `enable()` самого поля либо оператор `enableWhen` на нём.\n */\n disabled?: boolean;\n item?: never;\n part?: never;\n children?: never;\n}","summary":"Поле: узел, привязанный к ручке значения — листу или массиву целиком (мультивыбор, теги, файлы).","description":"Поле: узел, привязанный к ручке значения — листу или массиву целиком (мультивыбор, теги, файлы).","params":[],"example":"```ts\n{ model: model.$.email, component: Input, componentProps: { label: 'Email' } }\n```","examplesCount":1,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaGroupHandle","kind":"interface","signature":"export interface SchemaGroupHandle {\n peek(): object | null | undefined;\n /** Признак «не ручка значения»: у листа и массива путь есть, у группы — нет. */\n readonly __path?: never;\n}","summary":"Ручка группы из дерева `model.$` (`model.$.address`).","description":"Ручка группы из дерева `model.$` (`model.$.address`).","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaNodeControl","kind":"interface","signature":"export interface SchemaNodeControl {\n /**\n * Принудительно скрыть или показать узел, минуя условие `hideWhen`.\n *\n * Только видимость: поля скрытого узла остаются включёнными, правила их проверяют, и шаг с\n * незаполненным скрытым полем не пройдёт — а замечаний на экране не будет. Скрываешь узел с\n * полями — выключи их тем же условием (`enableWhen`).\n */\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 /**\n * @internal Записать условие скрытия узла (`hideWhen`). Возвращает снятие: оно убирает условие,\n * только если его не сменило более позднее.\n */\n addHideCondition(condition: () => boolean): () => void;\n /**\n * @internal Записать обработчик проп-события компонента узла (`onComponentEvent`). Возвращает\n * снятие: оно убирает обработчик, только если его не сменил более поздний.\n */\n addEventHandler(event: string, handler: (...args: any[]) => any): () => void;\n /**\n * @internal Записать хук жизненного цикла узла (`onMount`, `onUnmount`). Возвращает снятие: оно\n * убирает хук, только если его не сменил более поздний.\n */\n addLifecycleHook<K extends keyof NodeLifecycleHooks>(\n hook: K,\n value: NonNullable<NodeLifecycleHooks[K]>\n ): () => void;\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":"SchemaNodeId","kind":"type","signature":"export type SchemaNodeId = string & { readonly __nodeId: true };","summary":"Идентификатор узла: `selector` автора, а у узла без него — позиционный путь от корня области (`#/0/2`).","description":"Идентификатор узла: `selector` автора, а у узла без него — позиционный путь от корня области\n(`#/0/2`). Уникален в пределах области.","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"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` узла: он же\nидентификатор узла в скомпилированной схеме области ({@link SchemaNodeId}).","params":[],"examplesCount":0,"sourcePath":"src/form/schema-controller.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaPartNode","kind":"interface","signature":"export interface SchemaPartNode extends SchemaNodeBase {\n /** Привязка — ручка группы `model.$.<группа>`. */\n model: SchemaGroupHandle;\n /** Часть схемы: под-модель группы → узел поддерева. */\n part: (model: any) => FormSchemaNode;\n item?: never;\n children?: never;\n}","summary":"Подформа: часть схемы, подключённая к группе модели.","description":"Подформа: часть схемы, подключённая к группе модели. Часть объявляется один раз и подключается\nк любой группе той же формы данных; привязки внутри части идут через `$` полученной под-модели.","params":[],"example":"```ts\n{ model: model.$.registrationAddress, part: address }\n```","examplesCount":1,"sourcePath":"src/form/types/schema-node.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 /**\n * @internal Записать эффект, живущий, пока область смонтирована (`renderEffect`). Возвращает\n * снятие.\n */\n addRenderEffect(effect: () => void | (() => void)): () => void;\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":"SchemaScopeId","kind":"type","signature":"export type SchemaScopeId = string & { readonly __scopeId: true };","summary":"Идентификатор области схемы: `#` — корень, `#/address` — подформа, `#/phones[3]` — строка массива (в скобках — стабильный ключ строки, который не меняется при перестановке).","description":"Идентификатор области схемы: `#` — корень, `#/address` — подформа, `#/phones[3]` — строка\nмассива (в скобках — стабильный ключ строки, который не меняется при перестановке).","params":[],"examplesCount":0,"topics":["compiled-schema"],"sourcePath":"src/form/schema/compiled.ts","entries":["."],"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,"sourcePath":"src/form/schema-subtree.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaTextPart","kind":"type","signature":"export type SchemaTextPart = string | number | Signal<any>;","summary":"Часть текста: литерал, число или сигнал (его значение выводится и обновляется на месте).","description":"Часть текста: литерал, число или сигнал (его значение выводится и обновляется на месте).","params":[],"examplesCount":0,"sourcePath":"src/form/schema/compiled.ts","entries":["."],"package":"@reformer/core"},{"name":"SchemaValueHandle","kind":"type","signature":"export type SchemaValueHandle = Signal<any> & { readonly __path: string };","summary":"Ручка значения из дерева `model.$`: лист (`model.$.email`) или массив целиком (`model.$.tags`).","description":"Ручка значения из дерева `model.$`: лист (`model.$.email`) или массив целиком\n(`model.$.tags`). Группа ручкой значения не является — у неё нет пути.","params":[],"examplesCount":0,"sourcePath":"src/form/types/schema-node.ts","entries":["."],"package":"@reformer/core"},{"name":"SubmissionDependencies","kind":"interface","signature":"export interface SubmissionDependencies<T> {\n /**\n * Проверка перед отправкой — попытка отправки: замечания раскрыты, при необходимости вся форма\n * тронута. `undefined` — правил нет: обработчик получает {@link SubmissionDependencies.values}.\n */\n readonly check: (() => Promise<ActionCheck<T>>) | undefined;\n /** Значения без проверки. */\n readonly values: () => T;\n}","summary":"Зависимости отправки.","description":"Зависимости отправки. Явные, чтобы модуль не тянул ни модель, ни ноды формы.","params":[],"examplesCount":0,"topics":["api-reference"],"sourcePath":"src/form/submission.ts","entries":["."],"package":"@reformer/core"},{"name":"SubmitResult","kind":"type","signature":"export type SubmitResult<TResult> =\n | { readonly status: 'submitted'; readonly result: TResult }\n | { readonly status: 'invalid' | 'error' | 'cancelled'; readonly validation: ValidationResult }\n | { readonly status: 'failed'; readonly error: unknown };","summary":"Итог отправки: `submitted` — обработчик отработал, `invalid` / `error` / `cancelled` — проверка не пропустила (обработчик не вызывался), `failed` — обработчик бросил исключение.","description":"Итог отправки: `submitted` — обработчик отработал, `invalid` / `error` / `cancelled` —\nпроверка не пропустила (обработчик не вызывался), `failed` — обработчик бросил исключение.\n\n`cancelled` — проверку отправки сменила следующая (вторая отправка, живой прогон) либо модель\nизменилась, пока проверка шла: правила видели прежние значения, и отправлять новые нельзя.\nЗамечаний такой итог не несёт — отправку повторяют.","params":[],"examplesCount":0,"topics":["api-reference","quick-start","api-signatures","common-patterns","complete-import","nonexistent-api","submit-and-reset","type-safety-recipes","async-validator-debounce","unified-contract","form-flow"],"sourcePath":"src/form/submission.ts","entries":["."],"package":"@reformer/core"},{"name":"syncFields","kind":"function","signature":"export function syncFields<T>(\n a: Signal<T>,\n b: Signal<NoInfer<T>>,\n options?: { transform?: (value: T) => T }\n): BehaviorCleanup","summary":"Двусторонняя синхронизация двух полей (опционально с трансформом `a → b`).","description":"Двусторонняя синхронизация двух полей (опционально с трансформом `a → b`).\nСходимость обеспечивается peek-guard'ом; флаг предотвращает лишние круги. При создании источник —\n`a`: `b` получает его значение (через `transform`), обратной записи при создании нет. Записи в\nоба поля в одном тике — побеждает последняя.","params":[],"example":"```typescript\nsyncFields(model.$.field1, model.$.field2);\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","nonexistent-api","cycle-detection","copy-from","sync-fields","transform-value","submit-and-reset"],"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","common-mistakes","complete-import","extended-mistakes","nonexistent-api","cycle-detection","sync-fields","transform-value","submit-and-reset"],"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":"unknownSchemaSelectors","kind":"function","signature":"export function unknownSchemaSelectors(\n maps: SchemaOverrideMaps,\n compiled: CompiledSchema\n): string[]","summary":"Селекторы, на которые в области записаны правила, но которых нет в её скомпилированной схеме.","description":"Селекторы, на которые в области записаны правила, но которых нет в её скомпилированной схеме.\nТакое правило ничего не делает — сборка и рендерер в dev предупреждают о нём.\n\nRef в проверку не входит: его разрешено брать по абсолютному пути модели\n(`phones.0.number`), а такой строки при сборке может ещё не быть.","params":[["maps","- Хранилище области."],["compiled","- Скомпилированная схема области ({@link CompiledSchema})."]],"returns":"Селекторы с правилами, не найденные в схеме области.","example":"```typescript\nconst scope = controller.scopeOf(model);\nunknownSchemaSelectors(scope.__overrideMaps, bundle.render.tree!); // ['mortgage'] — узла нет\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(options?: UrlValidatorOptions): Rule<string | null | undefined>","summary":"Фабрика валидатора URL.","description":"Фабрика валидатора URL.\n\nПустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}\nдля обязательности).\n\nКонтракт адреса: разбор — `new URL`; хост — `localhost`, домен с буквенной зоной (в том числе\nкириллический и punycode), IPv4 или `[IPv6]`; порт допустим. Запрещены пробелы и userinfo\n(`user:pass@`). Протокол необязателен (`example.com`), пока не задан `requireProtocol`; по\nумолчанию разрешены `http` и `https`, чужой протокол — замечание `url_protocol` с\n`params.allowedProtocols`. Свой `code` из опций заменяет оба встроенных кода.","params":[["options","- Опции валидатора {@link UrlValidatorOptions}"]],"returns":"Правило {@link Rule} для строкового поля","example":"Проверка URL\n```typescript\nimport { defineValidation, validate } from '@reformer/core/validation';\nimport { required, url } from '@reformer/core/validators';\n\n// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.\nconst validation = defineValidation<MyForm>(({ model }) => {\nvalidate(model.$.website, [required(), url({ code: 'website.url' })]);\n// Требовать протокол и ограничить схему только https:\nvalidate(model.$.homepage, [url({ requireProtocol: true, allowedProtocols: ['https'] })]);\n});\n```","examplesCount":1,"topics":["api-reference","api-signatures","async-validator-debounce","issue-messages"],"sourcePath":"src/form/validators/url.ts","entries":["./validators"],"package":"@reformer/core","usage":5},{"name":"UrlValidatorOptions","kind":"interface","signature":"export interface UrlValidatorOptions extends RuleOptions {\n /** Требовать протокол (`схема://`). По умолчанию `false`: `example.com` допустим. */\n requireProtocol?: boolean;\n /**\n * Разрешённые протоколы (без `://`), сравнение без учёта регистра, например `['https']`. Не\n * задан или пуст — `http` и `https`. Значение с иным протоколом даёт замечание `url_protocol`;\n * при заданном списке — и значение без протокола.\n */\n allowedProtocols?: string[];\n}","summary":"Опции валидатора {@link url}.","description":"Опции валидатора {@link url}. Расширяют {@link RuleOptions} (`code`, `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: FormArray<T>): number","summary":"React-хук для подписки только на длину массива.","description":"React-хук для подписки только на длину массива.\n\nОптимизированная версия {@link useFormControl} для FormArray, которая\nподписывается только на длину массива. Компонент не будет ре-рендериться\nпри изменении значений вложенных полей.","params":[["control","- FormArray для подписки"]],"returns":"Текущая длина массива","example":"```tsx\nfunction ArrayRenderer({ arrayNode }) {\n const length = useArrayLength(arrayNode);\n\n return (\n <div>\n {arrayNode.map((item, index) => (\n <ItemRenderer key={arrayNode.keyAt(index)} item={item} />\n ))}\n </div>\n );\n}\n```","examplesCount":1,"topics":["api-reference","complete-import","arrays","nonexistent-api","reading-values","array-operations"],"sourcePath":"src/platforms/react/hooks/useArrayLength.ts","entries":["./react"],"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":["./react"],"package":"@reformer/core"},{"name":"useFormBundle","kind":"function","signature":"export function useFormBundle<B extends FormBundleLike>(factory: () => B): B","summary":"Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте (не при SSR — там эффекты не выполняются); при размонтировании снять сборку.","description":"Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте\n(не при SSR — там эффекты не выполняются); при размонтировании снять сборку.","params":[["factory","- Фабрика бандла, обычно `() => createForm({…})`. Сборкой владеет хук: при\nразмонтировании он вызывает её `dispose()`, поэтому фабрика должна собирать НОВУЮ форму, а не\nвозвращать чужую, живущую дольше компонента."]],"returns":"Стабильный бандл.","example":"```tsx\nconst credit = useFormBundle(() => createForm<CreditForm>({ model: createCreditModel() }));\n```","examplesCount":1,"topics":["api-reference","quick-start","api-signatures","common-mistakes","complete-import","multi-step","project-structure","ui-components","nonexistent-api","transform-value","submit-and-reset","async-preload","type-safety-recipes","validation-strategy","unified-contract","form-flow"],"sourcePath":"src/platforms/react/hooks/use-form-bundle.ts","entries":["./react"],"package":"@reformer/core","usage":38},{"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={() => void bundle?.validate()}>Проверить</button>;\n}\n```","examplesCount":1,"topics":["api-reference","unified-contract"],"sourcePath":"src/platforms/react/form-bundle-context.ts","entries":["./react"],"package":"@reformer/core"},{"name":"useFormControl","kind":"function","signature":"export function useFormControl(\n control: FormControl<unknown> | FormArray<object> | undefined\n): FieldControlState<unknown> | ArrayControlState<object>","summary":"React-хук для подписки на состояние формы (FormControl или FormArray).","description":"React-хук для подписки на состояние формы (FormControl или FormArray).\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, issues, touched и т.д.).\n\nДля подписки только на значение используйте {@link useFormControlValue} -\nэто предотвратит лишние ре-рендеры при изменении других свойств.","params":[["control","- FormControl, FormArray или undefined"]],"returns":"Объект состояния {@link FieldControlState} или {@link ArrayControlState}","example":"Текстовое поле с валидацией\n```tsx\nimport { useFormControl, useIssueMessage } from '@reformer/core/react';\nimport type { FormControl } from '@reformer/core';\n\ninterface TextFieldProps {\ncontrol: FormControl<string>;\nlabel: string;\n}\n\nfunction TextField({ control, label }: TextFieldProps) {\nconst {\nvalue,\ndisabled,\nshouldShowError,\nshouldShowIssues,\nissues,\npending\n} = useFormControl(control);\n// Текст ошибки даёт резолвер: у правил без своего сообщения `message` пуст.\nconst message = useIssueMessage();\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{shouldShowIssues && issues[0] && (\n<span className=\"error\" role=\"alert\">\n {message(issues[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","async-validator-debounce","i18n","issue-messages"],"sourcePath":"src/platforms/react/hooks/useFormControl.ts","entries":["./react"],"package":"@reformer/core","usage":8},{"name":"useFormControlValue","kind":"function","signature":"export function useFormControlValue<T>(control: FormControl<T>): T","summary":"React-хук для подписки только на значение поля.","description":"React-хук для подписки только на значение поля.\n\nОптимизированная версия {@link useFormControl}, которая подписывается\nтолько на сигнал `value`. Компонент не будет ре-рендериться при изменении\n`issues`, `touched`, `valid` и других свойств состояния.\n\n## Когда использовать\n\n- **Условный рендеринг** на основе значения другого поля\n- **Вычисляемые значения** зависящие от значения поля\n- **Read-only отображение** значения без интерактивности\n- **Оптимизация производительности** когда не нужны другие свойства состояния\n\n## Когда НЕ использовать\n\nЕсли компоненту нужны `issues`, `touched`, `disabled` или другие свойства -\nиспользуйте {@link useFormControl}. Множественные подписки на один контрол\nчерез разные хуки менее эффективны, чем одна подписка через `useFormControl`.","params":[["control","- FormControl для подписки на значение"]],"returns":"Текущее значение поля","example":"Условный рендеринг секции\n```tsx\nimport { useFormControlValue } from '@reformer/core/react';\n\ninterface FormFields {\nhasShipping: FormControl<boolean>;\nshippingAddress: FormGroup<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","FormControl"],"topics":["api-reference","common-patterns","common-mistakes","troubleshooting","complete-import","nonexistent-api","conditional-fields","reading-values"],"sourcePath":"src/platforms/react/hooks/useFormControlValue.ts","entries":["./react"],"package":"@reformer/core","usage":29},{"name":"useFormFlow","kind":"function","signature":"export function useFormFlow(flow: FormFlow): FormFlowSnapshot","summary":"Подписаться на поток шагов: компонент перерисовывается при смене шага, пройденных шагов и состояния проверки.","description":"Подписаться на поток шагов: компонент перерисовывается при смене шага, пройденных шагов и\nсостояния проверки. Переходы — методами самого потока (`flow.next()`, `flow.goTo(id)`).","params":[["flow","- Поток шагов (`bundle.flow`)."]],"returns":"","example":"```tsx\nfunction Steps({ flow }: { flow: FormFlow }) {\n const { current, isLast, validating } = useFormFlow(flow);\n return (\n <button disabled={isLast || validating} onClick={() => void flow.next()}>\n Далее ({current})\n </button>\n );\n}\n```","examplesCount":1,"topics":["api-reference","common-mistakes","complete-import","multi-step","nonexistent-api","form-flow"],"sourcePath":"src/platforms/react/hooks/use-form-flow.ts","entries":["./react"],"package":"@reformer/core"},{"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 = defineValidation<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":["api-reference","api-signatures","complete-import","nonexistent-api","validation-strategy"],"sourcePath":"src/platforms/react/hooks/use-form-validation.ts","entries":["./react"],"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: ValidationDefinition<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":["./react"],"package":"@reformer/core"},{"name":"UseFormValidationResult","kind":"interface","signature":"export interface UseFormValidationResult {\n /**\n * Попытка отправки: полный прогон; при замечаниях раскрыта вся форма, `afterFirstSubmit`\n * переходит в живую фазу. `true` — только статус `valid`. Для `onSubmit`. Стабильная ссылка.\n */\n submit: () => Promise<boolean>;\n /**\n * Проверка без отправки: полный прогон, раскрыты замечания проверенных полей, фаза\n * `afterFirstSubmit` не меняется. Для кнопки «Проверить». Стабильная ссылка.\n */\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":["./react"],"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":["api-reference","nonexistent-api","i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./react"],"package":"@reformer/core"},{"name":"useIssueMessage","kind":"function","signature":"export function useIssueMessage(messages?: IssueMessages): (issue: ValidationIssue) => string","summary":"Резолвер текста замечания валидации: словарь формы → словарь активной локали по коду → встроенный английский → код.","description":"Резолвер текста замечания валидации: словарь формы → словарь активной локали по коду →\nвстроенный английский → код. Идентичность функции меняется вместе с локалью и словарём,\nпоэтому показанные замечания переводятся при смене языка без повторной валидации.\n\n`FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете замечания\nсвоим компонентом.","params":[["messages","- Словарь формы (`defineIssueMessages`). Стабильная ссылка — константа\nмодуля, иначе резолвер будет пересоздаваться на каждый рендер."]],"example":"```tsx\nconst messages = defineIssueMessages({ 'phone.required': 'Укажите телефон' });\n\nfunction FieldIssues({ control }: { control: FormControl<string> }) {\n const message = useIssueMessage(messages);\n const { issues } = useFormControl(control);\n return <>{issues.map((issue) => <span key={issue.code}>{message(issue)}</span>)}</>;\n}\n```","examplesCount":1,"topics":["api-reference","common-mistakes","complete-import","ui-components","nonexistent-api","reading-values","async-validator-debounce","i18n","issue-messages"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./react"],"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":["api-reference","nonexistent-api","i18n"],"sourcePath":"src/platforms/react/i18n/hooks.ts","entries":["./react"],"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({ code: 'loanAmount.required' }), 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","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy","i18n","unified-contract","issue-messages","form-flow"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":172},{"name":"validateAsync","kind":"function","signature":"export function validateAsync<TField>(\n sig: PathAwareSignal<TField>,\n rules: AsyncRule<TField>[]\n): void","summary":"Асинхронные правила поля.","description":"Асинхронные правила поля. Раннер дожидается их и прокидывает `AbortSignal` для отмены\nустаревших ответов.\n\nОтклонённое правило (сеть, исключение) — сбой проверки: он попадает в `failures` результата\nпрогона, статус прогона становится `error`, а на поле ложится блокирующая ошибка `ruleFailed`\n(текст — из словаря локали). Отмена устаревшего прогона сбоем не считается.","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' };\n}]);\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-mistakes","complete-import","extended-mistakes","nonexistent-api","async-validator-debounce"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":11},{"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: ValidationDefinition<T>,\n options?: { touch?: boolean }\n): Promise<boolean>","summary":"Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма): собрать результат и разнести ошибки по нодам формы.","description":"Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма): собрать результат и разнести ошибки по\nнодам формы. Возвращает `true` только для статуса `valid`: ошибки, сбой правила и отмена дают\n`false` (**fail-closed**). Чтобы различить их, берите {@link runValidation} либо\n`bundle.validate()` / `bundle.validateStep(id)` сборки формы.\n\nГашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)`\nне мешают друг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema))\nотменяется через `AbortSignal`.\n\n⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности\n`schema`. Инлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ\nпрогон без дедупликации — держите схемы в `const` / `defineValidation`.","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","api-signatures","common-patterns","common-mistakes","troubleshooting","complete-import","formschema","arrays","multi-step","extended-mistakes","nonexistent-api","conditional-fields","revalidate-when","submit-and-reset","async-preload","type-safety-recipes","async-validator-debounce","validation-strategy","unified-contract","form-flow"],"sourcePath":"src/form/validation/run.ts","entries":["./validation"],"package":"@reformer/core","usage":29},{"name":"validateWhen","kind":"function","signature":"export function validateWhen(cond: () => boolean, cb: () => void): void","summary":"Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля ГАСЯТСЯ (пустой bucket → `setIssues([])`).","description":"Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля\nГАСЯТСЯ (пустой bucket → `setIssues([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).","params":[],"examplesCount":0,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","extended-mistakes","project-structure","nonexistent-api","conditional-fields","unified-contract"],"sourcePath":"src/form/validation/operators.ts","entries":["./validation"],"package":"@reformer/core","usage":9},{"name":"ValidationDefinition","kind":"type","signature":"export type ValidationDefinition<T> = (scope: ValidationScope<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","form-flow"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core","usage":6},{"name":"ValidationFailure","kind":"interface","signature":"export interface ValidationFailure {\n /** Ручка поля `model.$.…`, чьё правило отклонилось. */\n readonly handle: PathAwareSignal<unknown>;\n /** То, чем правило отклонилось (ошибка сети, исключение). */\n readonly error: unknown;\n}","summary":"Правило, которое не вернуло результат.","description":"Правило, которое не вернуло результат.","params":[],"examplesCount":0,"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"ValidationIssue","kind":"interface","signature":"export interface ValidationIssue {\n /** Код правила: встроенный (`required`, `minLength`) либо свой (`loanAmount.required`). */\n readonly code: string;\n /** Значения для подстановки в текст: порог правила, фактическое значение. */\n readonly params?: Readonly<Record<string, unknown>>;\n /** `'error'` (по умолчанию) блокирует отправку, `'warning'` показывается, но не блокирует. */\n readonly severity?: 'error' | 'warning';\n}","summary":"Замечание валидации: что нарушено (`code`) и с какими числами (`params`).","description":"Замечание валидации: что нарушено (`code`) и с какими числами (`params`). Текста у замечания\nнет — отображаемую строку по коду даёт слой представления (`resolveIssueMessage` из\n`@reformer/core/i18n`): словарь формы → словарь локали → встроенный английский → сам код.\nПоэтому одно и то же замечание показывается на любом языке без повторной проверки.","params":[],"example":"```ts\nconst tooShort: ValidationIssue = { code: 'minLength', params: { minLength: 8 } };\nconst softLimit: ValidationIssue = { code: 'loanAmount.high', severity: 'warning' };\n```","examplesCount":1,"topics":["api-reference","api-signatures","common-patterns","common-mistakes","complete-import","extended-mistakes","nonexistent-api","conditional-fields","reading-values","array-operations","revalidate-when","type-safety-recipes","async-validator-debounce","i18n","unified-contract","issue-messages"],"sourcePath":"src/form/types/contracts.ts","entries":["."],"package":"@reformer/core","usage":29},{"name":"ValidationResult","kind":"interface","signature":"export interface ValidationResult {\n readonly status: ValidationStatus;\n /** Ошибки по ручке поля `model.$.…`; путь — метаданные ручки (`handle.__path`). */\n readonly issues: ReadonlyMap<PathAwareSignal<unknown>, readonly ValidationIssue[]>;\n /** Правила, которые не вернули результат (сеть, исключение). */\n readonly failures: readonly ValidationFailure[];\n}","summary":"Результат прогона схемы валидации — см.","description":"Результат прогона схемы валидации — см. `runValidation`.","params":[],"examplesCount":0,"topics":["api-reference","quick-start","api-signatures","complete-import","multi-step","nonexistent-api","submit-and-reset","async-validator-debounce","validation-strategy","unified-contract","form-flow"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"ValidationScope","kind":"interface","signature":"export interface ValidationScope<T> {\n /** Модель области. Привязки правил — через её `$`: `validate(model.$.email, [...])`. */\n readonly model: FormModel<T>;\n /**\n * Правило над несколькими полями: `check` получает снимок модели области (`model.get()`) и\n * вешает ошибку на поле `handle`. Тип снимка выведен из схемы — приводить его не нужно.\n *\n * @example\n * ```ts\n * defineValidation<Loan>(({ model, cross }) => {\n * cross(model.$.initialPayment, (loan) =>\n * loan.initialPayment > loan.propertyValue ? tooHigh : null\n * );\n * });\n * ```\n */\n cross(handle: PathAwareSignal<unknown>, check: (snapshot: T) => ValidationIssue | null): void;\n}","summary":"Область схемы валидации — то, что схема получает аргументом.","description":"Область схемы валидации — то, что схема получает аргументом.\n\nВ корне прогона область — вся форма; в схеме, подключённой через `apply` / `applyEach`, —\nпод-модель группы или строки массива. Поэтому правила подформы пишутся один раз и не знают, где\nона стоит.","params":[],"examplesCount":0,"topics":["api-reference","api-signatures"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"ValidationStatus","kind":"type","signature":"export type ValidationStatus = 'valid' | 'invalid' | 'cancelled' | 'error';","summary":"Чем закончился прогон схемы: - `valid` — блокирующих ошибок нет (`severity: 'warning'` не блокирует); - `invalid` — есть блокирующие ошибки; - `error` — хотя бы одно async-правило не вернуло результат (сеть, исключение); - `cancelled` — прогон устарел: для той же пары (model, schema) запущен следующ","description":"Чем закончился прогон схемы:\n - `valid` — блокирующих ошибок нет (`severity: 'warning'` не блокирует);\n - `invalid` — есть блокирующие ошибки;\n - `error` — хотя бы одно async-правило не вернуло результат (сеть, исключение);\n - `cancelled` — прогон устарел: для той же пары (model, schema) запущен следующий.\n\nПриоритет при нескольких причинах: `cancelled` → `error` → `invalid` → `valid`.","params":[],"examplesCount":0,"topics":["api-signatures"],"sourcePath":"src/form/validation/types.ts","entries":["./validation"],"package":"@reformer/core"},{"name":"ValidationStrategyKind","kind":"type","signature":"export type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';","summary":"Когда запускается schema-валидация: - `submit` (по умолчанию) — только явным прогоном (`validate()`, отправка); - `blur` — при потере фокуса поля (каждый уход из любого листа, и повторный тоже); - `change` — на каждый ввод (с опц.","description":"Когда запускается schema-валидация:\n- `submit` (по умолчанию) — только явным прогоном (`validate()`, отправка);\n- `blur` — при потере фокуса поля (каждый уход из любого листа, и повторный тоже);\n- `change` — на каждый ввод (с опц. `debounce`);\n- `afterFirstSubmit` — тихо до первой попытки отправки (`run({ submit: true })`), затем live\n (см. `liveAfterSubmit`). Проверка без отправки (`validate()`) фазу не переключает.","params":[],"examplesCount":0,"topics":["api-reference","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":"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","nonexistent-api","reading-values","compute-vs-watch","sync-fields","type-safety-recipes"],"sourcePath":"src/model/behaviors-value.ts","entries":["./model"],"package":"@reformer/core"},{"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"}]}
|