@reformer/core 11.0.0-beta.6 → 11.0.0-beta.8

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.
Files changed (113) hide show
  1. package/README.md +7 -3
  2. package/dist/behaviors.js +216 -179
  3. package/dist/create-model-BBRHhHwK.js +30 -0
  4. package/dist/en-CjAqn7h0.js +41 -0
  5. package/dist/form/behaviors/collections.d.ts +14 -1
  6. package/dist/form/behaviors/context.d.ts +18 -2
  7. package/dist/form/behaviors/index.d.ts +3 -1
  8. package/dist/form/behaviors/internals.d.ts +9 -1
  9. package/dist/form/behaviors/schema-operators.d.ts +76 -0
  10. package/dist/form/behaviors/types.d.ts +15 -3
  11. package/dist/form/create-core-form.d.ts +5 -3
  12. package/dist/form/create-form.d.ts +28 -53
  13. package/dist/form/form-bundle.d.ts +136 -0
  14. package/dist/form/index.d.ts +6 -1
  15. package/dist/form/nodes/field-node.d.ts +2 -2
  16. package/dist/form/nodes/group-node.d.ts +1 -1
  17. package/dist/form/nodes/model-array-node.d.ts +10 -5
  18. package/dist/form/schema-controller.d.ts +137 -0
  19. package/dist/form/schema-subtree.d.ts +28 -0
  20. package/dist/form/signal-node-registry.d.ts +22 -16
  21. package/dist/form/types/contracts.d.ts +6 -0
  22. package/dist/form/types/deep-schema.d.ts +2 -2
  23. package/dist/form/types/form-proxy.d.ts +1 -1
  24. package/dist/form/types/schema-node.d.ts +37 -12
  25. package/dist/form/types/validation-schema.d.ts +13 -1
  26. package/dist/form/validation/context.d.ts +6 -1
  27. package/dist/form/validation/index.d.ts +1 -1
  28. package/dist/form/validation/operators.d.ts +74 -7
  29. package/dist/form/validation/types.d.ts +2 -1
  30. package/dist/form/validators/validation-error.d.ts +8 -0
  31. package/dist/i18n/descriptor.d.ts +54 -0
  32. package/dist/i18n/en.json.d.ts +45 -0
  33. package/dist/i18n/loader.d.ts +71 -0
  34. package/dist/i18n/locale-store.d.ts +39 -0
  35. package/dist/i18n/locale.d.ts +90 -0
  36. package/dist/i18n/message-format.d.ts +116 -0
  37. package/dist/i18n/resolve-localized.d.ts +17 -0
  38. package/dist/i18n/ru.json.d.ts +45 -0
  39. package/dist/i18n/translator.d.ts +50 -0
  40. package/dist/i18n/validation-message.d.ts +18 -0
  41. package/dist/i18n.d.ts +31 -0
  42. package/dist/i18n.js +303 -0
  43. package/dist/index-BrNkQj1y.js +105 -0
  44. package/dist/index.js +737 -743
  45. package/dist/loader-Dv_Hl7JC.js +216 -0
  46. package/dist/locale/en.d.ts +2 -0
  47. package/dist/locale/en.js +5 -0
  48. package/dist/locale/en.json +42 -0
  49. package/dist/locale/index.d.ts +18 -0
  50. package/dist/locale/ru.d.ts +2 -0
  51. package/dist/locale/ru.js +41 -0
  52. package/dist/locale/ru.json +42 -0
  53. package/dist/locale.d.ts +2 -0
  54. package/dist/locale.js +9 -0
  55. package/dist/model/create-model.d.ts +17 -3
  56. package/dist/model/index.d.ts +8 -3
  57. package/dist/model/model-nodes.d.ts +67 -4
  58. package/dist/model/model-signals-proxy.d.ts +33 -1
  59. package/dist/model/model-value-proxy.d.ts +76 -0
  60. package/dist/model/types.d.ts +26 -9
  61. package/dist/model-value-proxy-caTmAiUd.js +419 -0
  62. package/dist/model.js +21 -17
  63. package/dist/node-BeV_lV5i.js +100 -0
  64. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  65. package/dist/platforms/react/hooks/types.d.ts +6 -3
  66. package/dist/platforms/react/hooks/useFormControlValue.d.ts +2 -1
  67. package/dist/platforms/react/i18n/context.d.ts +13 -0
  68. package/dist/platforms/react/i18n/hooks.d.ts +59 -0
  69. package/dist/platforms/react/i18n/provider.d.ts +52 -0
  70. package/dist/platforms/react/index.d.ts +1 -0
  71. package/dist/strategy-DgzELWno.js +204 -0
  72. package/dist/validation-error-B-U3RsV0.js +11 -0
  73. package/dist/validation.js +7 -6
  74. package/dist/validators/email.js +5 -8
  75. package/dist/validators/future-date.js +8 -11
  76. package/dist/validators/integer.js +4 -7
  77. package/dist/validators/is-date.js +5 -8
  78. package/dist/validators/is-number.js +4 -7
  79. package/dist/validators/max-age.js +8 -11
  80. package/dist/validators/max-date.js +8 -11
  81. package/dist/validators/max-length.js +5 -8
  82. package/dist/validators/max.js +4 -7
  83. package/dist/validators/min-age.js +8 -11
  84. package/dist/validators/min-date.js +8 -11
  85. package/dist/validators/min-length.js +5 -8
  86. package/dist/validators/min.js +4 -7
  87. package/dist/validators/multiple-of.js +5 -8
  88. package/dist/validators/non-negative.js +4 -7
  89. package/dist/validators/non-zero.js +4 -7
  90. package/dist/validators/past-date.js +8 -11
  91. package/dist/validators/pattern.js +5 -8
  92. package/dist/validators/phone.js +6 -9
  93. package/dist/validators/required.js +4 -7
  94. package/dist/validators/url.js +9 -14
  95. package/dist/validators.js +84 -106
  96. package/docs/llms/03-api-signatures.md +10 -4
  97. package/docs/llms/09-formschema.md +5 -5
  98. package/docs/llms/10-arrays.md +52 -3
  99. package/docs/llms/16-ui-components.md +2 -2
  100. package/docs/llms/18-conditional-fields.md +1 -1
  101. package/docs/llms/21-array-operations.md +26 -0
  102. package/docs/llms/28-submit-and-reset.md +3 -3
  103. package/docs/llms/29-async-preload.md +4 -4
  104. package/docs/llms/33-validation-strategy.md +2 -2
  105. package/docs/llms/34-i18n.md +272 -0
  106. package/docs/llms/35-unified-contract.md +246 -0
  107. package/llms-index.json +1 -1
  108. package/llms.txt +2746 -229
  109. package/package.json +22 -3
  110. package/dist/create-model-Dxh9cfLp.js +0 -97
  111. package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
  112. package/dist/node-DGEFMTQu.js +0 -18
  113. package/dist/strategy-DA3JwxsR.js +0 -170
@@ -1,18 +1,34 @@
1
1
  import { BehaviorCleanup } from '../../index';
2
+ import { SchemaController } from '../schema-controller';
2
3
  import { BehaviorScope, FormBehavior } from './types';
3
4
  /** Зарегистрировать произвольную отписку в активной схеме. */
4
5
  export declare function onDispose(cleanup: BehaviorCleanup): void;
5
- /** Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов). */
6
+ /**
7
+ * Зарегистрировать отписку, если оператор вызван внутри схемы поведения; вне схемы — ничего.
8
+ * Для операторов, которые работают и сами по себе (правила узлов схемы).
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function onDisposeIfActive(cleanup: BehaviorCleanup): void;
13
+ /** Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов). */
6
14
  export declare function getScope<T>(): BehaviorScope<T>;
15
+ /**
16
+ * Схема-контроллер сборки, в которой идёт активная схема: под-схемы (`apply`, `applyEach`)
17
+ * получают через него область своей под-модели.
18
+ *
19
+ * @internal
20
+ */
21
+ export declare function getController(op?: string): SchemaController;
7
22
  /**
8
23
  * Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
9
24
  *
10
25
  * @example
11
26
  * ```ts
12
- * export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
27
+ * export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
13
28
  * compute(model.$.total, () => model.price * model.qty);
14
29
  * enableWhen([model.$.city], () => Boolean(model.country));
15
30
  * onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
31
+ * hideWhen(schema.node('delivery'), () => model.country === '');
16
32
  * });
17
33
  * ```
18
34
  */
@@ -2,7 +2,7 @@
2
2
  * Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
3
3
  *
4
4
  * Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
5
- * `defineFormBehavior(({ model, form }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
5
+ * `defineFormBehavior(({ model, form, schema }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
6
6
  * автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
7
7
  * владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
8
8
  * ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
@@ -26,3 +26,5 @@ export type { Signal, ReadonlySignal, BehaviorCleanup } from './types';
26
26
  export { onDispose, getScope, defineFormBehavior, effect, defer } from './context';
27
27
  export { compute, computeFrom, copyFrom, onChange, enableWhen, disableWhen, transformValue, resetWhen, syncFields, revalidateWhen, } from './operators';
28
28
  export { applyEach, exclusiveFlag, aggregateInto, apply } from './collections';
29
+ export { hideWhen, onComponentEvent, renderEffect, onInit, onMount, onUnmount, } from './schema-operators';
30
+ export type { SchemaScope, SchemaNodeControl } from '../schema-controller';
@@ -13,6 +13,15 @@ export type GroupSignals = Record<string, unknown> & {
13
13
  * read-only агрегат вместо рекурсивного `writeGroup`.
14
14
  */
15
15
  export declare const isLeafSignal: (v: unknown) => v is Signal<unknown>;
16
+ /**
17
+ * Ручка значения целиком: лист либо узел-массив дерева `$`.
18
+ *
19
+ * Массив копируется и читается ЗНАЧЕНИЕМ — заменой целиком, а не обходом по ключам: у его узла
20
+ * собственный ключ один (`length`), и обход, годный для группы, отдавал бы вместо массива мусор.
21
+ * Для `enableWhen` массив при этом остаётся НЕ листом ({@link isLeafSignal}): его нода ищется по
22
+ * пути, а она бывает и полем, и массивом под-форм.
23
+ */
24
+ export declare const isValueHandle: (v: unknown) => v is Signal<unknown>;
16
25
  export declare const asArray: <X>(v: X | X[]) => X[];
17
26
  export declare function readGroup(g: GroupSignals): Record<string, unknown>;
18
27
  export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>): void;
@@ -23,4 +32,3 @@ export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>
23
32
  * синхронные мутации цикла не порождают → не затрагиваются.
24
33
  */
25
34
  export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
26
- export declare function getByPath(root: unknown, path: string): any;
@@ -0,0 +1,76 @@
1
+ import { SchemaNodeControl, SchemaScope } from '../schema-controller';
2
+ /**
3
+ * Скрывать узел, пока условие истинно.
4
+ *
5
+ * Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
6
+ * Принудительное `schema.node(selector).setHidden(...)` главнее условия.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * hideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');
11
+ * ```
12
+ */
13
+ export declare function hideWhen(node: SchemaNodeControl, condition: () => boolean): void;
14
+ /**
15
+ * Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
16
+ *
17
+ * Обработчик получает те же аргументы, что и исходный проп компонента, и перекрывает одноимённый
18
+ * проп из `componentProps`.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
23
+ * await submitApplication(model.get());
24
+ * });
25
+ * ```
26
+ */
27
+ export declare function onComponentEvent(node: SchemaNodeControl, event: string, handler: (...args: any[]) => any): void;
28
+ /**
29
+ * Реактивный эффект, живущий, пока схема области смонтирована рендерером.
30
+ *
31
+ * В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
32
+ * (`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const wizard = schema.node('wizard').getRef<FormWizardHandle>();
37
+ * renderEffect(schema, () => {
38
+ * if (model.loanType === 'mortgage') wizard.current?.goToStep(1);
39
+ * });
40
+ * ```
41
+ */
42
+ export declare function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void;
43
+ /**
44
+ * Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
45
+ * хук, способный повлиять на первый рендер, — внутри можно править пропсы через
46
+ * `schema.node(selector).patchProps({ … })`.
47
+ *
48
+ * К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
49
+ * {@link onMount} / {@link onUnmount}.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * onInit(schema.node('summary'), () => {
54
+ * schema.node('summary').patchProps({ currency: detectCurrency() });
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function onInit(_node: SchemaNodeControl, fn: () => void): void;
59
+ /**
60
+ * Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * onMount(schema.node('data-boundary'), () => void loadApplication());
65
+ * ```
66
+ */
67
+ export declare function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void;
68
+ /**
69
+ * Хук перед размонтированием узла.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
74
+ * ```
75
+ */
76
+ export declare function onUnmount(node: SchemaNodeControl, fn: () => void): void;
@@ -1,14 +1,26 @@
1
1
  import { Signal, ReadonlySignal } from '@preact/signals-core';
2
2
  import { BehaviorCleanup, FormModel, FormProxy } from '../../index';
3
- /** Контекст схемы поведения: модель (значения/сигналы) + форма (ноды). */
3
+ import { SchemaController, SchemaScope } from '../schema-controller';
4
+ /**
5
+ * Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).
6
+ *
7
+ * `schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении
8
+ * подформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов
9
+ * (`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.
10
+ */
4
11
  export type BehaviorScope<T> = {
5
12
  model: FormModel<T>;
6
13
  form: FormProxy<T>;
14
+ schema: SchemaScope;
7
15
  };
8
16
  /** Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`. */
9
17
  export interface FormBehavior<T> {
10
- /** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
11
- __run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
18
+ /**
19
+ * @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.
20
+ * `controller` — схема-контроллер сборки; без него поведение получает собственный, который
21
+ * никто не читает (модель без рендерера).
22
+ */
23
+ __run(model: FormModel<T>, form: FormProxy<T>, controller?: SchemaController): BehaviorCleanup;
12
24
  }
13
25
  export type { Signal, ReadonlySignal, BehaviorCleanup };
14
26
  /** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */
@@ -27,7 +27,7 @@ export interface CreateFormConfigBase<T, B> {
27
27
  seed?: (model: FormModel<T>) => void;
28
28
  /**
29
29
  * Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
30
- * `createForm`: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
30
+ * сборкой формы: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
31
31
  * поэтому такой префилл выполняется здесь.
32
32
  */
33
33
  setup?: (bundle: B) => void;
@@ -41,14 +41,16 @@ export interface CoreForm<T> {
41
41
  /** Конфиг {@link createCoreForm}. */
42
42
  export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
43
43
  /**
44
- * Билдер схемы формы. Именно функция, а не готовое дерево: листья схемы держат сами сигналы
45
- * модели (`value: model.$.email`), поэтому построить дерево до модели нечем.
44
+ * Билдер схемы формы. Именно функция, а не готовое дерево: узлы схемы держат ручки модели
45
+ * (`model: model.$.email`), поэтому построить дерево до модели нечем.
46
46
  */
47
47
  schema?: (model: FormModel<T>) => FormSchemaNode;
48
48
  }
49
49
  /**
50
50
  * Собрать модель, форму и валидацию за один проход.
51
51
  *
52
+ * @deprecated Пишите `createForm` — та же сборка, в бандле вдобавок дерево для рендерера.
53
+ *
52
54
  * @typeParam T - Форма данных модели.
53
55
  * @param config - {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,
54
56
  * `validation`, `seed`, `setup`.
@@ -2,6 +2,7 @@ import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
2
2
  import { FormSchemaNode } from './types/schema-node';
3
3
  import { FormModel } from '../model/types';
4
4
  import { FormBehavior } from './behaviors';
5
+ import { SchemaController } from './schema-controller';
5
6
  /**
6
7
  * Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
7
8
  * конфиг полей (component/componentProps) — из единой схемы.
@@ -21,24 +22,32 @@ export interface CreateFormFromModelArgs<T> {
21
22
  * заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
22
23
  */
23
24
  behavior?: FormBehavior<T>;
25
+ /**
26
+ * Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт
27
+ * сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.
28
+ * Без него поведение пишет правила в собственный контроллер, который никто не читает.
29
+ */
30
+ schemaController?: SchemaController;
24
31
  }
25
32
  /**
26
33
  * Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).
27
34
  *
28
35
  * Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
29
36
  * ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
30
- * модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
31
- * top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
32
- * роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
37
+ * модели, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы
38
+ * под-форм как {@link ModelArrayNode} (на любой глубине), заполняет реестр ручка→нода (для
39
+ * `enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное
40
+ * поведение (cleanup живёт на форме).
33
41
  *
34
- * Обычно вызывается неявно через {@link createForm} с аргументом `{ model, schema }` — прямой вызов
35
- * нужен редко (например, для построения формы элемента массива).
42
+ * Обычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту
43
+ * фабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента
44
+ * массива, тесты, свой слой сборки.
36
45
  *
37
46
  * @typeParam T - Тип модели данных формы
38
47
  * @param args - Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}
39
48
  * @returns Типизированная форма с Proxy-доступом к полям {@link FormProxy}
40
49
  *
41
- * @example Форма из модели + схемы (эквивалент `createForm({ model, schema })`)
50
+ * @example Форма из модели + схемы (эквивалент `createFormFromModel({ model, schema })`)
42
51
  * ```typescript
43
52
  * import { createModel, createFormFromModel } from '@reformer/core';
44
53
  *
@@ -65,69 +74,35 @@ export interface CreateFormFromModelArgs<T> {
65
74
  * console.log(model.email); // 'user@mail.com'
66
75
  * ```
67
76
  *
68
- * @see {@link createForm} - основная фабрика (диспетчеризует сюда при аргументе `{ model, schema }`)
77
+ * @see `createForm` — сборка одним вызовом: модель, форма, валидация и дерево для рендера
69
78
  * @group Utilities
70
79
  */
71
80
  export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
72
81
  /**
73
- * Создать форму из {@link FormModel} + единой схемы (архитектура M1, рекомендуемый путь).
74
- *
75
- * @group Utilities
82
+ * Создать форму из готового конфига группы.
76
83
  *
77
- * @param args - Модель данных, layout-схема (component/componentProps) и (опционально) поведение
84
+ * @param config - Конфиг {@link GroupNode}
78
85
  * @returns Типизированная форма с Proxy-доступом к полям
79
- *
80
- * @example Архитектура M1: `createModel` + layout-схема + `createForm({ model, schema })`
81
- * ```typescript
82
- * import { createModel, createForm } from '@reformer/core';
83
- * import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
84
- * import { required, email, minLength } from '@reformer/core/validators';
85
- *
86
- * interface UserForm {
87
- * email: string;
88
- * password: string;
89
- * }
90
- *
91
- * const model = createModel<UserForm>({ email: '', password: '' });
92
- *
93
- * // Layout-схема НЕ несёт validators — только component/componentProps.
94
- * const schema = {
95
- * children: [
96
- * { value: model.$.email, component: Input },
97
- * { value: model.$.password, component: Input },
98
- * ],
99
- * };
100
- *
101
- * const form = createForm<UserForm>({ model, schema });
102
- * form.email.setValue('test@mail.com');
103
- *
104
- * // Валидация — ОТДЕЛЬНАЯ схема, прогон внешним раннером (ошибки сами доезжают до нод):
105
- * const validation = defineValidationSchema<UserForm>(({ model }) => {
106
- * validate(model.$.email, [required(), email()]);
107
- * validate(model.$.password, [required(), minLength(8)]);
108
- * });
109
- * const ok = await validateModel(model, validation);
110
- * ```
86
+ * @group Utilities
111
87
  */
112
- export declare function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
113
- export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
88
+ export declare function createLegacyForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
114
89
  /**
115
- * Создать форму только со схемой полей.
90
+ * Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.
116
91
  *
117
- * @deprecated Legacy / back-compat: плоская `FormSchema` с инлайн-значениями (`value: ''`) — путь
118
- * ДО архитектуры M1. Для нового кода используйте перегрузку `createForm({ model, schema })`
119
- * (значения принадлежат {@link FormModel}, а правила — отдельной схеме `defineValidationSchema`,
120
- * которую прогоняет `validateModel`; поля `validators` у узла layout-схемы нет).
92
+ * Значения живут в нодах, модели нет: поведение (`defineFormBehavior`) и внешняя валидация
93
+ * (`validateModel`) к такой форме не подключаются. Для нового кода — {@link createFormFromModel}
94
+ * либо сборка одним вызовом.
121
95
  *
122
96
  * @param schema - Схема полей формы
123
97
  * @returns Типизированная форма с Proxy-доступом к полям
98
+ * @group Utilities
124
99
  *
125
- * @example Legacy (back-compat) — плоская схема без модели
100
+ * @example
126
101
  * ```typescript
127
- * const form = createForm<UserForm>({
102
+ * const form = createLegacyForm<UserForm>({
128
103
  * email: { value: '', component: Input },
129
104
  * password: { value: '', component: Input },
130
105
  * });
131
106
  * ```
132
107
  */
133
- export declare function createForm<T>(schema: FormSchema<T>): FormProxy<T>;
108
+ export declare function createLegacyForm<T>(schema: FormSchema<T>): FormProxy<T>;
@@ -0,0 +1,136 @@
1
+ import { FormModel } from '../model/types';
2
+ import { FormBehavior } from './behaviors';
3
+ import { SchemaController, SchemaNodeControl } from './schema-controller';
4
+ import { FormSchemaNode } from './types/schema-node';
5
+ import { FormProxy } from './types/index';
6
+ import { FormValidation, FormValidationBundle } from './validation/config';
7
+ import { ValidationSchema } from './validation';
8
+ /**
9
+ * Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до
10
+ * рендерера вместе с ним.
11
+ *
12
+ * @group Utilities
13
+ */
14
+ export interface ResolvedSchema {
15
+ /** Корневой узел дерева. */
16
+ tree: FormSchemaNode;
17
+ /** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */
18
+ fieldWrapper?: unknown;
19
+ /** Граница ошибок — компонент, которым рендерер оборачивает дерево. */
20
+ errorBoundary?: unknown;
21
+ }
22
+ /**
23
+ * Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра
24
+ * есть сторонние реализации, и ядро документ само не разбирает.
25
+ *
26
+ * @group Utilities
27
+ */
28
+ export interface SchemaResolver {
29
+ resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;
30
+ }
31
+ /**
32
+ * Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.
33
+ *
34
+ * @group Utilities
35
+ */
36
+ export interface FormRender {
37
+ /** Корневой узел дерева схемы; `undefined`, если схема не задана. */
38
+ readonly tree: FormSchemaNode | undefined;
39
+ /** Схема-контроллер сборки: области корня, строк массивов и подформ. */
40
+ readonly controller: SchemaController;
41
+ /** Управление узлом КОРНЕВОЙ области по его `selector`. */
42
+ node(selector: string): SchemaNodeControl;
43
+ /** Обёртка поля из реестра (JSON-вариант). */
44
+ readonly fieldWrapper?: unknown;
45
+ /** Граница ошибок из реестра (JSON-вариант). */
46
+ readonly errorBoundary?: unknown;
47
+ }
48
+ /**
49
+ * Результат {@link createForm}.
50
+ *
51
+ * @typeParam T - Форма данных модели.
52
+ * @group Utilities
53
+ */
54
+ export interface FormBundle<T> {
55
+ model: FormModel<T>;
56
+ form: FormProxy<T>;
57
+ /** Собранная валидация; есть, если в конфиге заданы правила. */
58
+ validation?: FormValidationBundle<T>;
59
+ render: FormRender;
60
+ }
61
+ /**
62
+ * Конфиг {@link createForm}.
63
+ *
64
+ * @typeParam T - Форма данных модели.
65
+ * @group Utilities
66
+ */
67
+ export interface CreateFormConfig<T> {
68
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
69
+ initial?: T;
70
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
71
+ model?: FormModel<T>;
72
+ /**
73
+ * Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.
74
+ * Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить
75
+ * дерево до модели нечем.
76
+ */
77
+ schema?: ((model: FormModel<T>) => FormSchemaNode) | object;
78
+ /** Реестр JSON-варианта: превращает документ схемы в дерево. */
79
+ registry?: SchemaResolver;
80
+ /** Поведение формы: значения, состояние нод и правила узлов схемы. */
81
+ behavior?: FormBehavior<T>;
82
+ /** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
83
+ validation?: FormValidation<T> | ValidationSchema<T>;
84
+ /**
85
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
86
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
87
+ * инициализации не срабатывают).
88
+ */
89
+ seed?: (model: FormModel<T>) => void;
90
+ /** Донастройка ПОСЛЕ сборки: получает готовый бандл. */
91
+ setup?: (bundle: FormBundle<T>) => void;
92
+ }
93
+ /** Бандл формы, у которой заданы правила: `validation` в нём есть всегда. */
94
+ type ValidatedFormBundle<T> = FormBundle<T> & {
95
+ validation: FormValidationBundle<T>;
96
+ };
97
+ /**
98
+ * Собрать модель, форму, валидацию и дерево для рендера за один проход.
99
+ *
100
+ * @typeParam T - Форма данных модели.
101
+ * @param config - {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,
102
+ * `behavior`, `validation`, `seed`, `setup`.
103
+ * @returns {@link FormBundle} — `{ model, form, validation?, render }`.
104
+ *
105
+ * @example Рендерер по TS-схеме
106
+ * ```tsx
107
+ * const credit = useFormBundle(() =>
108
+ * createForm<CreditForm>({
109
+ * model: createCreditModel(),
110
+ * schema: creditSchema, // (model) => узел
111
+ * behavior: creditBehavior,
112
+ * validation: { steps: { loan: loanRules }, extras: crossRules },
113
+ * })
114
+ * );
115
+ * return <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;
116
+ * ```
117
+ *
118
+ * @example React-руками — та же сборка, разметка в JSX
119
+ * ```tsx
120
+ * const credit = useFormBundle(() => createForm<CreditForm>({ initial, schema: creditSchema }));
121
+ * return <FormField control={credit.form.loanType} />;
122
+ * ```
123
+ *
124
+ * @example JSON — документ схемы и реестр
125
+ * ```tsx
126
+ * const credit = useFormBundle(() =>
127
+ * createForm<CreditForm>({ model: createCreditModel(), schema: creditJson, registry })
128
+ * );
129
+ * return <FormRenderer form={credit} />;
130
+ * ```
131
+ */
132
+ export declare function createForm<T extends object>(config: CreateFormConfig<T> & {
133
+ validation: FormValidation<T> | ValidationSchema<T>;
134
+ }): ValidatedFormBundle<T>;
135
+ export declare function createForm<T extends object>(config: CreateFormConfig<T>): FormBundle<T>;
136
+ export {};
@@ -18,8 +18,13 @@ export { GroupNode } from './nodes/group-node';
18
18
  export { ArrayNode } from './nodes/array-node';
19
19
  export { ModelArrayNode } from './nodes/model-array-node';
20
20
  export type { ModelArrayControl } from './nodes/model-array-node';
21
- export { createForm, createFormFromModel } from './create-form';
21
+ export { createForm } from './form-bundle';
22
+ export type { CreateFormConfig, FormBundle, FormRender, ResolvedSchema, SchemaResolver, } from './form-bundle';
23
+ export { createFormFromModel, createLegacyForm } from './create-form';
22
24
  export type { CreateFormFromModelArgs } from './create-form';
25
+ export { schemaSubtree } from './schema-subtree';
26
+ export { createSchemaController, createSchemaScope, createSchemaOverrideMaps, unknownSchemaSelectors, } from './schema-controller';
27
+ export type { SchemaController, SchemaScope, SchemaNodeControl, SchemaOverrideMaps, NodeLifecycleHooks, } from './schema-controller';
23
28
  export { createCoreForm } from './create-core-form';
24
29
  export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
25
30
  export { buildValidation } from './validation/config';
@@ -196,10 +196,10 @@ export declare class FieldNode<T> extends FormNode<T> {
196
196
  *
197
197
  * @example
198
198
  * Пример node-level: плоская схема с инлайн-значениями (back-compat путь до M1). В layout-схеме
199
- * `createForm({ model, schema })` поля `validators` нет — правила живут в `defineValidationSchema`.
199
+ * `createFormFromModel({ model, schema })` поля `validators` нет — правила живут в `defineValidationSchema`.
200
200
  * ```typescript
201
201
  * // Сценарий 1: Instant feedback после submit
202
- * const form = createForm({
202
+ * const form = createLegacyForm({
203
203
  * email: {
204
204
  * value: '',
205
205
  * component: Input,
@@ -7,7 +7,7 @@ import { SubmitOptions, SubmitResult } from '../form-submitter';
7
7
  * GroupNode - узел для группы полей
8
8
  *
9
9
  * Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
10
- * (M1: `createForm({ model, schema })`); schema-валидация/behavior живут на слое модели
10
+ * (M1: `createFormFromModel({ model, schema })`); schema-валидация/behavior живут на слое модели
11
11
  * (`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.
12
12
  *
13
13
  * @group Nodes
@@ -7,8 +7,8 @@ import { FormProxy } from '../types/form-proxy';
7
7
  export interface ModelArrayControl<TItem extends object> {
8
8
  readonly length: number;
9
9
  at(index: number): FormModel<TItem> | undefined;
10
- push(item: TItem): void;
11
- insertAt(index: number, item: TItem): void;
10
+ push(item?: TItem): void;
11
+ insertAt(index: number, item?: TItem): void;
12
12
  removeAt(index: number): void;
13
13
  move(from: number, to: number): void;
14
14
  swap(a: number, b: number): void;
@@ -25,7 +25,7 @@ export interface ModelArrayControl<TItem extends object> {
25
25
  * reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
26
26
  * контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
27
27
  *
28
- * Обычно создаётся не напрямую, а `createForm({ model, schema })`: когда в схеме встречается узел
28
+ * Обычно создаётся не напрямую, а `createFormFromModel({ model, schema })`: когда в схеме встречается узел
29
29
  * массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
30
30
  * `ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
31
31
  *
@@ -40,7 +40,7 @@ export interface ModelArrayControl<TItem extends object> {
40
40
  * qty: { value: item.$.qty, component: Input },
41
41
  * });
42
42
  *
43
- * const form = createForm({
43
+ * const form = createFormFromModel({
44
44
  * model,
45
45
  * schema: {
46
46
  * children: [{ array: model.rows, item: rowItem }],
@@ -79,13 +79,18 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
79
79
  /**
80
80
  * @param control Реактивный массив модели (`model.<path>`).
81
81
  * @param buildItem Строитель формы элемента по его под-модели (инъекция, чтобы избежать цикла
82
- * импорта с `create-form`). Обычно `(item) => createForm({ model: item, schema: itemSchema(item) })`.
82
+ * импорта с `create-form`). Обычно `(item) => createFormFromModel({ model: item, schema: itemSchema(item) })`.
83
83
  */
84
84
  constructor(control: ModelArrayControl<T>, buildItem: (item: FormModel<T>) => FormProxy<T>);
85
85
  at(index: number): FormProxy<T> | undefined;
86
86
  map<R>(fn: (item: FormProxy<T>, index: number) => R): R[];
87
87
  forEach(fn: (item: FormProxy<T>, index: number) => void): void;
88
+ /**
89
+ * Добавить элемент. Без значения — элемент по шаблону массива модели (`arrayOf(blank)` либо
90
+ * `initialValue` узла-массива схемы); шаблона нет — ошибка модели.
91
+ */
88
92
  push(item?: Partial<T>): void;
93
+ /** Вставить элемент по индексу; без значения — по шаблону, как у {@link push}. */
89
94
  insert(index: number, item?: Partial<T>): void;
90
95
  removeAt(index: number): void;
91
96
  /**