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

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/llms.txt CHANGED
@@ -113,14 +113,15 @@ const { value, errors, disabled } = useFormControl(control.loanType);
113
113
  > `Checkbox`-обёртки с `label`-prop'ами — это anti-pattern. См.
114
114
  > `find_recipe(package="@reformer/ui-kit", topic="form-field-integration")`.
115
115
 
116
- Архитектура M1: сначала создаётся **модель данных** (`createModel`), затем **форма**
117
- (`createForm({ model, schema })`), где схема привязывает поля к сигналам модели
118
- (`model.$.field`). Layout-схема НЕ несёт валидаторов — валидация живёт в отдельной
116
+ Архитектура M1: **модель данных** — источник истины, схема привязывает поля к её сигналам
117
+ (`model.$.field`), а сборка идёт ОДНИМ вызовом `createCoreForm` (для рендера ui-kit;
118
+ в рендерерах — `createReactForm` / `createJsonForm`), который создаёт модель, строит форму,
119
+ запускает поведение и собирает валидацию. Layout-схема НЕ несёт валидаторов — валидация живёт в отдельной
119
120
  схеме `defineValidationSchema` из `@reformer/core/validation` и запускается внешним
120
121
  раннером `validateModel(model, schema)`.
121
122
 
122
123
  ```typescript
123
- import { createModel, createForm, type FormProxy } from '@reformer/core';
124
+ import { createCoreForm, createModel, useFormBundle, type FormProxy } from '@reformer/core';
124
125
  import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
125
126
  import { required, email } from '@reformer/core/validators';
126
127
  import { FormField, Input, Button } from '@reformer/ui-kit';
@@ -155,8 +156,14 @@ const contactValidation = defineValidationSchema<ContactForm>(({ model }) => {
155
156
  validate(model.$.email, [required({ message: 'Email is required' }), email({ message: 'Invalid email' })]);
156
157
  });
157
158
 
158
- // 5. Form — ноды поверх сигналов модели
159
- const form = createForm<ContactForm>({ model, schema });
159
+ // 5. Сборка ОДНИМ вызовом: модель + ноды поверх её сигналов + валидация.
160
+ // В React оборачивают в useFormBundle — ленивый useState, фабрика зовётся один раз.
161
+ const contact = createCoreForm<ContactForm>({
162
+ model,
163
+ schema: buildSchema, // билдер (model) => tree
164
+ validation: contactValidation,
165
+ });
166
+ const form = contact.form;
160
167
 
161
168
  // 6. Use in React component — thin JSX, FormField does ALL heavy lifting
162
169
  function ContactFormComponent() {
@@ -227,7 +234,7 @@ const schema = {
227
234
  properties: { array: model.properties, item: propertyItem },
228
235
  };
229
236
 
230
- const form = createForm<MyForm>({ model, schema });
237
+ const { form } = createCoreForm<MyForm>({ model, schema: () => schema });
231
238
 
232
239
  // Операции над массивом — на модели:
233
240
  model.properties.push({ type: 'apartment', description: '', estimatedValue: 0 });
@@ -1653,7 +1660,7 @@ src/
1653
1660
  │
1654
1661
  ├── forms/
1655
1662
  │ └── [form-name]/ # Form module — flat, one file per concern
1656
- │ ├── index.tsx # entry + whole form: createModel→createForm→<FormWizard> with all steps inline; arrays via FormArraySection
1663
+ │ ├── index.tsx # entry + whole form: ONE createCoreForm call → <FormWizard> with all steps inline; arrays via FormArraySection
1657
1664
  │ ├── types.ts # form type + enums + { value, label } option type + constant dictionaries
1658
1665
  │ ├── model.ts # createModel + initial values + empty-array-element factories
1659
1666
  │ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
@@ -1698,11 +1705,15 @@ export const creditApplicationBehavior = defineFormBehavior<CreditApplicationFor
1698
1705
  });
1699
1706
 
1700
1707
  // forms/credit-application/index.tsx — entry: assembles the form, renders <FormWizard> with all steps inline
1701
- import { createForm } from '@reformer/core';
1702
- export const createCreditApplicationForm = () => {
1703
- const model = createCreditApplicationModel();
1704
- return createForm({ model, schema: creditApplicationSchema(model), behavior: creditApplicationBehavior });
1705
- };
1708
+ import { createCoreForm } from '@reformer/core';
1709
+ // ONE call: model + form + behavior + validation. In the page: useFormBundle(createCreditApplicationForm).
1710
+ export const createCreditApplicationForm = () =>
1711
+ createCoreForm({
1712
+ model: createCreditApplicationModel(),
1713
+ schema: creditApplicationSchema, // builder (model) => tree
1714
+ behavior: creditApplicationBehavior,
1715
+ validation: creditApplicationValidation, // { steps, extras } → bundle.validation
1716
+ });
1706
1717
  ```
1707
1718
 
1708
1719
  ### Scaling up: folders (large forms)
@@ -1728,10 +1739,10 @@ forms/
1728
1739
  │
1729
1740
  ├── schema/ # The form definition
1730
1741
  │ ├── model.ts # createModel factory + initial values + array-element factories
1731
- │ ├── schema.ts # createForm schema tree ({ value: model.$.x, component })
1742
+ │ ├── schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
1732
1743
  │ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
1733
1744
  │ ├── behavior.ts # defineFormBehavior(...)
1734
- │ └── create-form.ts # Assembly: createForm({ model, schema, behavior })
1745
+ │ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
1735
1746
  │
1736
1747
  └── components/
1737
1748
  ├── steps/ # One component per wizard step
@@ -4646,6 +4657,43 @@ export type BehaviorCleanup = () => void;
4646
4657
 
4647
4658
  _Source: src/state/behaviors-value.ts_
4648
4659
 
4660
+ ### buildValidation
4661
+
4662
+ **Kind:** `function`
4663
+
4664
+ Собрать {@link FormValidationBundle} из правил. Принимает либо готовую схему (сахар для простых
4665
+ форм), либо {@link FormValidation} с шагами и стратегией.
4666
+
4667
+ Схема для `validateAll` собирается ОДИН раз на бандл — иначе ломается дедупликация прогонов.
4668
+
4669
+ **Signature:**
4670
+ ```typescript
4671
+ export function buildValidation<T>(
4672
+ model: FormModel<T>,
4673
+ validation?: FormValidation<T> | ValidationSchema<T>
4674
+ ): FormValidationBundle<T> | undefined
4675
+ ```
4676
+
4677
+ **Parameters:**
4678
+ - `model` — - Модель, по которой идут прогоны.
4679
+ - `validation` — - Правила; `undefined` — валидации у формы нет.
4680
+
4681
+ **Returns:** Бандл валидации либо `undefined`, если правила не заданы.
4682
+
4683
+ **Examples:**
4684
+
4685
+ ```ts
4686
+ const v = buildValidation(model, {
4687
+ steps: { loan: loanRules, applicant: applicantRules, confirm: null },
4688
+ extras: crossFieldRules,
4689
+ strategy: 'blur',
4690
+ });
4691
+ await v!.validateStep(1); // правила шага `loan`
4692
+ await v!.validateAll(); // все шаги + extras
4693
+ ```
4694
+
4695
+ _Source: src/form/validation-config.ts_
4696
+
4649
4697
  ### computeFrom
4650
4698
 
4651
4699
  **Kind:** `function`
@@ -4765,6 +4813,75 @@ stampRuntime('@reformer/core', CORE_RUNTIME_TOKEN);
4765
4813
 
4766
4814
  _Source: src/runtime-token.ts_
4767
4815
 
4816
+ ### CoreForm
4817
+
4818
+ **Kind:** `interface`
4819
+
4820
+ Результат {@link createCoreForm}: модель, форма и (если заданы правила) собранная валидация.
4821
+
4822
+ **Signature:**
4823
+ ```typescript
4824
+ export interface CoreForm<T> {
4825
+ model: FormModel<T>;
4826
+ form: FormProxy<T>;
4827
+ validation?: FormValidationBundle<T>;
4828
+ }
4829
+ ```
4830
+
4831
+ _Source: src/form/create-core-form.ts_
4832
+
4833
+ ### createCoreForm
4834
+
4835
+ **Kind:** `function`
4836
+
4837
+ Собрать модель, форму и валидацию за один проход.
4838
+
4839
+ **Signature:**
4840
+ ```typescript
4841
+ export function createCoreForm<T extends object>(config: CreateCoreFormConfig<T>): CoreForm<T>
4842
+ ```
4843
+
4844
+ **Parameters:**
4845
+ - `config` — - {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,
4846
+ `validation`, `seed`, `setup`.
4847
+
4848
+ **Returns:**
4849
+
4850
+ **Examples:**
4851
+
4852
+ ```tsx
4853
+ const credit = useFormBundle(() =>
4854
+ createCoreForm<CreditForm>({
4855
+ model: createCreditModel(),
4856
+ schema: buildCreditSchema,
4857
+ behavior: creditBehavior,
4858
+ validation: { steps: { loan: loanRules, confirm: null }, extras: crossRules },
4859
+ })
4860
+ );
4861
+ return <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;
4862
+ ```
4863
+
4864
+ _Source: src/form/create-core-form.ts_
4865
+
4866
+ ### CreateCoreFormConfig
4867
+
4868
+ **Kind:** `interface`
4869
+
4870
+ Конфиг {@link createCoreForm}.
4871
+
4872
+ **Signature:**
4873
+ ```typescript
4874
+ export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
4875
+ /**
4876
+ * Билдер схемы формы. Именно функция, а не готовое дерево: листья схемы держат сами сигналы
4877
+ * модели (`value: model.$.email`), поэтому построить дерево до модели нечем.
4878
+ */
4879
+ schema?: (model: FormModel<T>) => FormSchemaNode;
4880
+ }
4881
+ ```
4882
+
4883
+ _Source: src/form/create-core-form.ts_
4884
+
4768
4885
  ### createForm
4769
4886
 
4770
4887
  **Kind:** `function`
@@ -4817,6 +4934,40 @@ const ok = await validateModel(model, validation);
4817
4934
 
4818
4935
  _Source: src/form/create-form.ts_
4819
4936
 
4937
+ ### CreateFormConfigBase
4938
+
4939
+ **Kind:** `interface`
4940
+
4941
+ Общая часть конфига всех фабрик формы.
4942
+
4943
+ **Signature:**
4944
+ ```typescript
4945
+ export interface CreateFormConfigBase<T, B> {
4946
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
4947
+ initial?: T;
4948
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
4949
+ model?: FormModel<T>;
4950
+ /** Декларативное поведение модели (compute/copyFrom/enableWhen/onChange). */
4951
+ behavior?: FormBehavior<T>;
4952
+ /** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
4953
+ validation?: FormValidation<T> | ValidationSchema<T>;
4954
+ /**
4955
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
4956
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
4957
+ * инициализации не срабатывают).
4958
+ */
4959
+ seed?: (model: FormModel<T>) => void;
4960
+ /**
4961
+ * Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
4962
+ * `createForm`: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
4963
+ * поэтому такой префилл выполняется здесь.
4964
+ */
4965
+ setup?: (bundle: B) => void;
4966
+ }
4967
+ ```
4968
+
4969
+ _Source: src/form/create-core-form.ts_
4970
+
4820
4971
  ### createFormFromModel
4821
4972
 
4822
4973
  **Kind:** `function`
@@ -5431,6 +5582,21 @@ todos.forEach((item, i) => {
5431
5582
 
5432
5583
  _Source: src/form/types/form-proxy.ts_
5433
5584
 
5585
+ ### FormBundleLike
5586
+
5587
+ **Kind:** `interface`
5588
+
5589
+ Минимум, который хук ожидает от бандла: опциональный контроллер живой валидации.
5590
+
5591
+ **Signature:**
5592
+ ```typescript
5593
+ export interface FormBundleLike {
5594
+ validation?: { controller: FormValidationController };
5595
+ }
5596
+ ```
5597
+
5598
+ _Source: src/form/hooks/use-form-bundle.ts_
5599
+
5434
5600
  ### FormControlsProxy
5435
5601
 
5436
5602
  **Kind:** `type`
@@ -5823,6 +5989,70 @@ if (submitter.submitting.value) {
5823
5989
 
5824
5990
  _Source: src/form/form-submitter.ts_
5825
5991
 
5992
+ ### FormValidation
5993
+
5994
+ **Kind:** `interface`
5995
+
5996
+ Правила валидации формы — ДАННЫЕ (без привязки к рендеру).
5997
+
5998
+ ⚠️ `schema` и значения `steps` обязаны быть **стабильными ссылками**: отмена устаревших прогонов
5999
+ ключуется по паре `(model, schema)` через `WeakMap`, поэтому инлайн-стрелка ломает дедупликацию —
6000
+ валидация начнёт возвращать `false` от уже отменённых прогонов. Держите их
6001
+ module-level-константами (`defineValidationSchema`).
6002
+
6003
+ **Signature:**
6004
+ ```typescript
6005
+ export interface FormValidation<T> {
6006
+ /** Полный набор правил — для submit и `validateAll`. Без него собирается из `steps` + `extras`. */
6007
+ schema?: ValidationSchema<T>;
6008
+ /**
6009
+ * Пошаговая валидация визарда: ключ = `selector` шага. `null` означает «шаг без правил» ЯВНО —
6010
+ * чтобы опечатка в ключе не выглядела как пустой шаг.
6011
+ */
6012
+ steps?: Record<string, ValidationSchema<T> | null>;
6013
+ /** Cross-field правила, которые проверяются только целиком (в `validateAll`), но не по шагам. */
6014
+ extras?: ValidationSchema<T>;
6015
+ /** Когда запускать живую валидацию. По умолчанию `'submit'` (реактивно молчит). */
6016
+ strategy?: ValidationStrategyKind;
6017
+ debounce?: number;
6018
+ /** Режим live-фазы для `afterFirstSubmit`. По умолчанию `'change'`. */
6019
+ liveAfterSubmit?: 'change' | 'blur';
6020
+ }
6021
+ ```
6022
+
6023
+ _Source: src/form/validation-config.ts_
6024
+
6025
+ ### FormValidationBundle
6026
+
6027
+ **Kind:** `interface`
6028
+
6029
+ Собранная валидация формы. Структурно совместима с `FormWizardConfig` и `StepValidationConfig`
6030
+ из `@reformer/cdk`, поэтому уходит в визард как есть: `config={bundle.validation}` либо
6031
+ `patchProps({ form, ...bundle.validation })`.
6032
+
6033
+ **Signature:**
6034
+ ```typescript
6035
+ export interface FormValidationBundle<T> {
6036
+ /** Полная схема (стабильная ссылка) — та, по которой идёт `validateAll`. */
6037
+ readonly schema: ValidationSchema<T>;
6038
+ /** Селекторы шагов в порядке объявления. Пусто, если `steps` не заданы. */
6039
+ readonly stepSelectors: readonly string[];
6040
+ /** Полный прогон с раскрытием ошибок. Синоним {@link FormValidationBundle.validateAll}. */
6041
+ validate(): Promise<boolean>;
6042
+ validateAll(): Promise<boolean>;
6043
+ /** Прогон правил одного шага: по номеру (1-based, как у визарда) или по селектору. */
6044
+ validateStep(step: number | string): Promise<boolean>;
6045
+ /** Контроллер живой валидации ШАГА; `null` — у шага нет правил либо стратегия `submit`. */
6046
+ createStepController(step: number | string): FormValidationController | null;
6047
+ /** Контроллер живой валидации ФОРМЫ. Армится хуком (`useFormBundle`), не фабрикой. */
6048
+ readonly controller: FormValidationController;
6049
+ /** Идёт ли прогон — реактивный сигнал (для тонкой подписки в UI). */
6050
+ readonly validating: ReadonlySignal<boolean>;
6051
+ }
6052
+ ```
6053
+
6054
+ _Source: src/form/validation-config.ts_
6055
+
5826
6056
  ### FormValue
5827
6057
 
5828
6058
  **Kind:** `type`
@@ -6983,6 +7213,31 @@ function ArrayRenderer({ arrayNode }) {
6983
7213
 
6984
7214
  _Source: src/form/hooks/useArrayLength.ts_
6985
7215
 
7216
+ ### useFormBundle
7217
+
7218
+ **Kind:** `function`
7219
+
7220
+ Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте
7221
+ (не при SSR — там эффекты не выполняются).
7222
+
7223
+ **Signature:**
7224
+ ```typescript
7225
+ export function useFormBundle<B extends FormBundleLike>(factory: () => B): B
7226
+ ```
7227
+
7228
+ **Parameters:**
7229
+ - `factory` — - Фабрика бандла, обычно `() => createCoreForm({…})`.
7230
+
7231
+ **Returns:** Стабильный бандл.
7232
+
7233
+ **Examples:**
7234
+
7235
+ ```tsx
7236
+ const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCreditModel() }));
7237
+ ```
7238
+
7239
+ _Source: src/form/hooks/use-form-bundle.ts_
7240
+
6986
7241
  ### useFormControl
6987
7242
 
6988
7243
  **Kind:** `function`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reformer/core",
3
- "version": "11.0.0-beta.2",
3
+ "version": "11.0.0-beta.3",
4
4
  "description": "Reactive form state management library for React with signals-based architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",