@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/dist/form/create-core-form.d.ts +70 -0
- package/dist/form/hooks/use-form-bundle.d.ts +26 -0
- package/dist/form/index.d.ts +6 -0
- package/dist/form/validation-config.d.ts +73 -0
- package/dist/form/validation-strategy.d.ts +8 -2
- package/dist/index.js +528 -474
- package/dist/validation.js +92 -92
- package/docs/llms/02-quick-start.md +14 -7
- package/docs/llms/15-project-structure.md +12 -8
- package/llms.txt +270 -15
- package/package.json +1 -1
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:
|
|
117
|
-
(`
|
|
118
|
-
|
|
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,
|
|
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.
|
|
159
|
-
|
|
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 =
|
|
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:
|
|
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 {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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 #
|
|
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:
|
|
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