@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/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
@@ -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
- /** Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`. */
6129
- readonly $: ModelSignals<T>;
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
- Дерево сигналов (escape-hatch `model.$`): листья → {@link PathAwareSignal},
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@reformer/core",
3
- "version": "11.0.0-beta.1",
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",
@@ -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
- };