@reformer/core 11.0.0-beta.1 → 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/behaviors.js +134 -134
- 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/form-model-DhW2HZOB.js +323 -0
- package/dist/index.js +535 -481
- package/dist/state/form-model.d.ts +14 -0
- package/dist/state/index.d.ts +2 -1
- package/dist/state/types.d.ts +39 -11
- package/dist/state.js +18 -18
- 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/docs/llms/19-reading-values.md +30 -1
- package/docs/llms/20-compute-vs-watch.md +9 -0
- package/llms.txt +374 -20
- package/package.json +1 -1
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form-model-DYCzGGKF.js +0 -286
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
|
|
@@ -2259,9 +2270,38 @@ model.$.email.peek(); // нереактивный снимок
|
|
|
2259
2270
|
model.$.address.city.value; // сигнал вложенного поля (≡ model.address.$.city у под-модели)
|
|
2260
2271
|
|
|
2261
2272
|
// весь объект
|
|
2262
|
-
model.get(); // снимок { email, address: { city }, ... } — для submit
|
|
2273
|
+
model.get(); // НЕреактивный снимок { email, address: { city }, ... } — для submit
|
|
2274
|
+
```
|
|
2275
|
+
|
|
2276
|
+
> ⚠️ `model.get()` и `model.isDirty()` читают через `peek()` — внутри `effect`/`computed` они НЕ
|
|
2277
|
+
> создают зависимостей. `computed(() => model.get())` никогда не пересчитается. Для реактивного
|
|
2278
|
+
> чтения всего объекта — узел дерева `$` (ниже).
|
|
2279
|
+
|
|
2280
|
+
### Подписка на группу и на модель целиком
|
|
2281
|
+
|
|
2282
|
+
Каждый узел дерева `$` — сигнал: лист отдаёт `PathAwareSignal`, а корень, вложенные объекты-группы
|
|
2283
|
+
и массивы — `ReadonlySignal` агрегированного значения поддерева. Поэтому подписаться можно на любом
|
|
2284
|
+
уровне, а не только на конкретном поле.
|
|
2285
|
+
|
|
2286
|
+
```typescript
|
|
2287
|
+
model.$.subscribe((all) => autosave(all)); // любое изменение модели; all: T целиком
|
|
2288
|
+
model.$.address.subscribe((addr) => ...); // только поддерево address
|
|
2289
|
+
model.$.address.value; // реактивный снимок группы
|
|
2290
|
+
model.$.address.peek(); // нереактивный снимок группы
|
|
2291
|
+
model.$.items.subscribe((rows) => ...); // массив: и правка элемента, и push/removeAt/move
|
|
2292
|
+
|
|
2293
|
+
// узел — обычный ReadonlySignal, поэтому принимается операциями слоя данных
|
|
2294
|
+
watchField(model.$.address, (addr) => geocode(addr));
|
|
2263
2295
|
```
|
|
2264
2296
|
|
|
2297
|
+
Дети узла доступны как раньше — `model.$.address.city` по-прежнему сигнал поля. Подписчик вызывается
|
|
2298
|
+
сразу с текущим значением (семантика `Signal.subscribe`), а `model.set(...)`/`model.reset()`
|
|
2299
|
+
уведомляют один раз, а не по разу на поле.
|
|
2300
|
+
|
|
2301
|
+
> ⚠️ Доступ к полю выигрывает у свойства сигнала: если в форме есть поле с именем `value`, `peek`,
|
|
2302
|
+
> `subscribe`, `valueOf`, `toString`, `toJSON` или `brand`, то `model.$.<группа>.<это имя>` вернёт
|
|
2303
|
+
> сигнал поля. `subscribe` при этом продолжает работать.
|
|
2304
|
+
|
|
2265
2305
|
### В behaviors — читаем model напрямую
|
|
2266
2306
|
|
|
2267
2307
|
`compute`/`onChange`/условия `when` читают значения из value-модели (`model.field`) —
|
|
@@ -2411,6 +2451,14 @@ import { watchField } from '@reformer/core';
|
|
|
2411
2451
|
const stop = watchField(model.$.country, () => { model.city = ''; });
|
|
2412
2452
|
```
|
|
2413
2453
|
|
|
2454
|
+
Источником может быть не только поле: узлы-контейнеры дерева `$` (корень, группы, массивы) — тоже
|
|
2455
|
+
`ReadonlySignal`, поэтому за поддеревом целиком следят без перечисления полей.
|
|
2456
|
+
|
|
2457
|
+
```typescript
|
|
2458
|
+
watchField(model.$.address, (addr) => geocode(addr)); // любое поле address
|
|
2459
|
+
watchField(model.$, (all) => autosave(all)); // любое поле формы
|
|
2460
|
+
```
|
|
2461
|
+
|
|
2414
2462
|
### Rule of Thumb
|
|
2415
2463
|
|
|
2416
2464
|
| Scenario | Use |
|
|
@@ -2419,6 +2467,7 @@ const stop = watchField(model.$.country, () => { model.city = ''; });
|
|
|
2419
2467
|
| Производное с явными зависимостями | `computeFrom` |
|
|
2420
2468
|
| Async-реакция, обновление componentProps | `onChange` (debounce + AbortSignal) |
|
|
2421
2469
|
| Простая синхронная реакция (примитив) | `watchField` |
|
|
2470
|
+
| Реакция на любое изменение группы/формы | `watchField(model.$.<группа>)` или `.subscribe` |
|
|
2422
2471
|
|
|
2423
2472
|
### Chained computeds
|
|
2424
2473
|
|
|
@@ -4608,6 +4657,43 @@ export type BehaviorCleanup = () => void;
|
|
|
4608
4657
|
|
|
4609
4658
|
_Source: src/state/behaviors-value.ts_
|
|
4610
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
|
+
|
|
4611
4697
|
### computeFrom
|
|
4612
4698
|
|
|
4613
4699
|
**Kind:** `function`
|
|
@@ -4727,6 +4813,75 @@ stampRuntime('@reformer/core', CORE_RUNTIME_TOKEN);
|
|
|
4727
4813
|
|
|
4728
4814
|
_Source: src/runtime-token.ts_
|
|
4729
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
|
+
|
|
4730
4885
|
### createForm
|
|
4731
4886
|
|
|
4732
4887
|
**Kind:** `function`
|
|
@@ -4779,6 +4934,40 @@ const ok = await validateModel(model, validation);
|
|
|
4779
4934
|
|
|
4780
4935
|
_Source: src/form/create-form.ts_
|
|
4781
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
|
+
|
|
4782
4971
|
### createFormFromModel
|
|
4783
4972
|
|
|
4784
4973
|
**Kind:** `function`
|
|
@@ -5393,6 +5582,21 @@ todos.forEach((item, i) => {
|
|
|
5393
5582
|
|
|
5394
5583
|
_Source: src/form/types/form-proxy.ts_
|
|
5395
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
|
+
|
|
5396
5600
|
### FormControlsProxy
|
|
5397
5601
|
|
|
5398
5602
|
**Kind:** `type`
|
|
@@ -5785,6 +5989,70 @@ if (submitter.submitting.value) {
|
|
|
5785
5989
|
|
|
5786
5990
|
_Source: src/form/form-submitter.ts_
|
|
5787
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
|
+
|
|
5788
6056
|
### FormValue
|
|
5789
6057
|
|
|
5790
6058
|
**Kind:** `type`
|
|
@@ -6077,6 +6345,29 @@ if (isGroupNode(node)) {
|
|
|
6077
6345
|
|
|
6078
6346
|
_Source: src/form/type-guards.ts_
|
|
6079
6347
|
|
|
6348
|
+
### isModelContainerSignal
|
|
6349
|
+
|
|
6350
|
+
**Kind:** `function`
|
|
6351
|
+
|
|
6352
|
+
Узел дерева `model.$` — контейнер (группа/массив), а не лист?
|
|
6353
|
+
|
|
6354
|
+
Контейнерный узел структурно совместим с `ReadonlySignal` (`peek`/`value`/`subscribe`), поэтому
|
|
6355
|
+
duck-typing «есть `peek` ⇒ это лист» на нём даёт ложное срабатывание. Этот guard — надёжный
|
|
6356
|
+
способ различить: обходчикам дерева нужно спускаться в контейнер, а не читать его целиком.
|
|
6357
|
+
|
|
6358
|
+
**Signature:**
|
|
6359
|
+
```typescript
|
|
6360
|
+
export function isModelContainerSignal(value: unknown): boolean
|
|
6361
|
+
```
|
|
6362
|
+
|
|
6363
|
+
**Examples:**
|
|
6364
|
+
|
|
6365
|
+
```typescript
|
|
6366
|
+
const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
|
|
6367
|
+
```
|
|
6368
|
+
|
|
6369
|
+
_Source: src/state/form-model.ts_
|
|
6370
|
+
|
|
6080
6371
|
### markDerived
|
|
6081
6372
|
|
|
6082
6373
|
**Kind:** `function`
|
|
@@ -6125,8 +6416,11 @@ API уровня модели (доступно на корне, под-моде
|
|
|
6125
6416
|
**Signature:**
|
|
6126
6417
|
```typescript
|
|
6127
6418
|
export interface ModelApi<T> {
|
|
6128
|
-
/**
|
|
6129
|
-
|
|
6419
|
+
/**
|
|
6420
|
+
* Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
|
|
6421
|
+
* Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
|
|
6422
|
+
*/
|
|
6423
|
+
readonly $: ModelGroupSignals<T>;
|
|
6130
6424
|
/** Снимок значений (без подписки) — для submit. */
|
|
6131
6425
|
get(): T;
|
|
6132
6426
|
/**
|
|
@@ -6270,6 +6564,41 @@ rows.length.value; // 1 (реактивная длина)
|
|
|
6270
6564
|
|
|
6271
6565
|
_Source: src/form/nodes/model-array-node.ts_
|
|
6272
6566
|
|
|
6567
|
+
### ModelArraySignals
|
|
6568
|
+
|
|
6569
|
+
**Kind:** `type`
|
|
6570
|
+
|
|
6571
|
+
Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
|
|
6572
|
+
и {@link ReadonlySignal} значения массива целиком (реагирует и на правку элемента, и на изменение
|
|
6573
|
+
состава — push/removeAt/move).
|
|
6574
|
+
|
|
6575
|
+
**Signature:**
|
|
6576
|
+
```typescript
|
|
6577
|
+
export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {
|
|
6578
|
+
readonly length: number;
|
|
6579
|
+
readonly [index: number]: ModelSignalNode<U>;
|
|
6580
|
+
};
|
|
6581
|
+
```
|
|
6582
|
+
|
|
6583
|
+
_Source: src/state/types.ts_
|
|
6584
|
+
|
|
6585
|
+
### ModelGroupSignals
|
|
6586
|
+
|
|
6587
|
+
**Kind:** `type`
|
|
6588
|
+
|
|
6589
|
+
Узел-группа дерева `model.$`: доступ к под-сигналам полей ({@link ModelSignals}) И одновременно
|
|
6590
|
+
{@link ReadonlySignal} агрегированного значения группы — `.value`/`.peek()`/`.subscribe()`.
|
|
6591
|
+
|
|
6592
|
+
⚠️ Поле формы с именем `value`/`peek`/`subscribe`/`valueOf`/`toString`/`toJSON`/`brand` затеняет
|
|
6593
|
+
одноимённое свойство сигнала (редкий краевой случай); `subscribe` при этом продолжает работать.
|
|
6594
|
+
|
|
6595
|
+
**Signature:**
|
|
6596
|
+
```typescript
|
|
6597
|
+
export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;
|
|
6598
|
+
```
|
|
6599
|
+
|
|
6600
|
+
_Source: src/state/types.ts_
|
|
6601
|
+
|
|
6273
6602
|
### ModelObject
|
|
6274
6603
|
|
|
6275
6604
|
**Kind:** `type`
|
|
@@ -6291,8 +6620,8 @@ _Source: src/state/types.ts_
|
|
|
6291
6620
|
|
|
6292
6621
|
**Kind:** `type`
|
|
6293
6622
|
|
|
6294
|
-
|
|
6295
|
-
|
|
6623
|
+
Карта под-сигналов объекта в дереве `model.$` (без свойств самого узла — их добавляет
|
|
6624
|
+
{@link ModelGroupSignals}).
|
|
6296
6625
|
|
|
6297
6626
|
**Signature:**
|
|
6298
6627
|
```typescript
|
|
@@ -6884,6 +7213,31 @@ function ArrayRenderer({ arrayNode }) {
|
|
|
6884
7213
|
|
|
6885
7214
|
_Source: src/form/hooks/useArrayLength.ts_
|
|
6886
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
|
+
|
|
6887
7241
|
### useFormControl
|
|
6888
7242
|
|
|
6889
7243
|
**Kind:** `function`
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const t = /* @__PURE__ */ new WeakMap();
|
|
2
|
-
function n(e) {
|
|
3
|
-
t.set(e, (t.get(e) ?? 0) + 1);
|
|
4
|
-
}
|
|
5
|
-
function i(e) {
|
|
6
|
-
const r = t.get(e);
|
|
7
|
-
r !== void 0 && (r <= 1 ? t.delete(e) : t.set(e, r - 1));
|
|
8
|
-
}
|
|
9
|
-
function o(e) {
|
|
10
|
-
return t.has(e);
|
|
11
|
-
}
|
|
12
|
-
export {
|
|
13
|
-
o as i,
|
|
14
|
-
n as m,
|
|
15
|
-
i as u
|
|
16
|
-
};
|