@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.
@@ -0,0 +1,70 @@
1
+ import { FormModel } from '../state/types';
2
+ import { FormSchemaNode } from './types/schema-node';
3
+ import { FormBehavior } from './behaviors';
4
+ import { FormProxy } from './types/index';
5
+ import { FormValidation, FormValidationBundle } from './validation-config';
6
+ import { ValidationSchema } from './validation-schema';
7
+ /**
8
+ * Общая часть конфига всех фабрик формы.
9
+ *
10
+ * @typeParam T - Форма данных модели.
11
+ * @typeParam B - Бандл конкретной фабрики (его получает {@link CreateFormConfigBase.setup}).
12
+ */
13
+ export interface CreateFormConfigBase<T, B> {
14
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
15
+ initial?: T;
16
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
17
+ model?: FormModel<T>;
18
+ /** Декларативное поведение модели (compute/copyFrom/enableWhen/onChange). */
19
+ behavior?: FormBehavior<T>;
20
+ /** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
21
+ validation?: FormValidation<T> | ValidationSchema<T>;
22
+ /**
23
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
24
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
25
+ * инициализации не срабатывают).
26
+ */
27
+ seed?: (model: FormModel<T>) => void;
28
+ /**
29
+ * Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
30
+ * `createForm`: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
31
+ * поэтому такой префилл выполняется здесь.
32
+ */
33
+ setup?: (bundle: B) => void;
34
+ }
35
+ /** Результат {@link createCoreForm}: модель, форма и (если заданы правила) собранная валидация. */
36
+ export interface CoreForm<T> {
37
+ model: FormModel<T>;
38
+ form: FormProxy<T>;
39
+ validation?: FormValidationBundle<T>;
40
+ }
41
+ /** Конфиг {@link createCoreForm}. */
42
+ export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
43
+ /**
44
+ * Билдер схемы формы. Именно функция, а не готовое дерево: листья схемы держат сами сигналы
45
+ * модели (`value: model.$.email`), поэтому построить дерево до модели нечем.
46
+ */
47
+ schema?: (model: FormModel<T>) => FormSchemaNode;
48
+ }
49
+ /**
50
+ * Собрать модель, форму и валидацию за один проход.
51
+ *
52
+ * @typeParam T - Форма данных модели.
53
+ * @param config - {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,
54
+ * `validation`, `seed`, `setup`.
55
+ * @returns {@link CoreForm}` <T>` — `{ model, form, validation? }`.
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * const credit = useFormBundle(() =>
60
+ * createCoreForm<CreditForm>({
61
+ * model: createCreditModel(),
62
+ * schema: buildCreditSchema,
63
+ * behavior: creditBehavior,
64
+ * validation: { steps: { loan: loanRules, confirm: null }, extras: crossRules },
65
+ * })
66
+ * );
67
+ * return <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;
68
+ * ```
69
+ */
70
+ export declare function createCoreForm<T extends object>(config: CreateCoreFormConfig<T>): CoreForm<T>;
@@ -0,0 +1,26 @@
1
+ import { FormValidationController } from '../validation-strategy';
2
+ /** Минимум, который хук ожидает от бандла: опциональный контроллер живой валидации. */
3
+ export interface FormBundleLike {
4
+ validation?: {
5
+ controller: FormValidationController;
6
+ };
7
+ }
8
+ /**
9
+ * Собрать форму один раз и держать её стабильной; живую стратегию валидации армировать в эффекте
10
+ * (не при SSR — там эффекты не выполняются).
11
+ *
12
+ * @typeParam B - Тип бандла конкретной фабрики.
13
+ * @param factory - Фабрика бандла, обычно `() => createCoreForm({…})`.
14
+ * @returns Стабильный бандл.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCreditModel() }));
19
+ * ```
20
+ *
21
+ * @remarks
22
+ * Передавайте именно фабрику: `useState` трактует функцию как ленивый инициализатор, а некоторые
23
+ * части бандла сами являются функциями (`RenderSchemaProxy`), поэтому `useFormBundle(bundle.render)`
24
+ * вызвал бы её вместо сохранения.
25
+ */
26
+ export declare function useFormBundle<B extends FormBundleLike>(factory: () => B): B;
@@ -19,6 +19,12 @@ export { ModelArrayNode } from './nodes/model-array-node';
19
19
  export type { ModelArrayControl } from './nodes/model-array-node';
20
20
  export { createForm, createFormFromModel } from './create-form';
21
21
  export type { CreateFormFromModelArgs } from './create-form';
22
+ export { createCoreForm } from './create-core-form';
23
+ export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
24
+ export { buildValidation } from './validation-config';
25
+ export type { FormValidation, FormValidationBundle } from './validation-config';
26
+ export { useFormBundle } from './hooks/use-form-bundle';
27
+ export type { FormBundleLike } from './hooks/use-form-bundle';
22
28
  export { enableWhen, disableWhen } from './behaviors-node';
23
29
  export { FormErrorHandler, ErrorStrategy } from './error-handler';
24
30
  export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
@@ -0,0 +1,73 @@
1
+ import { ReadonlySignal } from '@preact/signals-core';
2
+ import { FormModel } from '../state/types';
3
+ import { ValidationSchema } from './validation-schema';
4
+ import { FormValidationController, ValidationStrategyKind } from './validation-strategy';
5
+ /**
6
+ * Правила валидации формы — ДАННЫЕ (без привязки к рендеру).
7
+ *
8
+ * ⚠️ `schema` и значения `steps` обязаны быть **стабильными ссылками**: отмена устаревших прогонов
9
+ * ключуется по паре `(model, schema)` через `WeakMap`, поэтому инлайн-стрелка ломает дедупликацию —
10
+ * валидация начнёт возвращать `false` от уже отменённых прогонов. Держите их
11
+ * module-level-константами (`defineValidationSchema`).
12
+ */
13
+ export interface FormValidation<T> {
14
+ /** Полный набор правил — для submit и `validateAll`. Без него собирается из `steps` + `extras`. */
15
+ schema?: ValidationSchema<T>;
16
+ /**
17
+ * Пошаговая валидация визарда: ключ = `selector` шага. `null` означает «шаг без правил» ЯВНО —
18
+ * чтобы опечатка в ключе не выглядела как пустой шаг.
19
+ */
20
+ steps?: Record<string, ValidationSchema<T> | null>;
21
+ /** Cross-field правила, которые проверяются только целиком (в `validateAll`), но не по шагам. */
22
+ extras?: ValidationSchema<T>;
23
+ /** Когда запускать живую валидацию. По умолчанию `'submit'` (реактивно молчит). */
24
+ strategy?: ValidationStrategyKind;
25
+ debounce?: number;
26
+ /** Режим live-фазы для `afterFirstSubmit`. По умолчанию `'change'`. */
27
+ liveAfterSubmit?: 'change' | 'blur';
28
+ }
29
+ /**
30
+ * Собранная валидация формы. Структурно совместима с `FormWizardConfig` и `StepValidationConfig`
31
+ * из `@reformer/cdk`, поэтому уходит в визард как есть: `config={bundle.validation}` либо
32
+ * `patchProps({ form, ...bundle.validation })`.
33
+ */
34
+ export interface FormValidationBundle<T> {
35
+ /** Полная схема (стабильная ссылка) — та, по которой идёт `validateAll`. */
36
+ readonly schema: ValidationSchema<T>;
37
+ /** Селекторы шагов в порядке объявления. Пусто, если `steps` не заданы. */
38
+ readonly stepSelectors: readonly string[];
39
+ /** Полный прогон с раскрытием ошибок. Синоним {@link FormValidationBundle.validateAll}. */
40
+ validate(): Promise<boolean>;
41
+ validateAll(): Promise<boolean>;
42
+ /** Прогон правил одного шага: по номеру (1-based, как у визарда) или по селектору. */
43
+ validateStep(step: number | string): Promise<boolean>;
44
+ /** Контроллер живой валидации ШАГА; `null` — у шага нет правил либо стратегия `submit`. */
45
+ createStepController(step: number | string): FormValidationController | null;
46
+ /** Контроллер живой валидации ФОРМЫ. Армится хуком (`useFormBundle`), не фабрикой. */
47
+ readonly controller: FormValidationController;
48
+ /** Идёт ли прогон — реактивный сигнал (для тонкой подписки в UI). */
49
+ readonly validating: ReadonlySignal<boolean>;
50
+ }
51
+ /**
52
+ * Собрать {@link FormValidationBundle} из правил. Принимает либо готовую схему (сахар для простых
53
+ * форм), либо {@link FormValidation} с шагами и стратегией.
54
+ *
55
+ * Схема для `validateAll` собирается ОДИН раз на бандл — иначе ломается дедупликация прогонов.
56
+ *
57
+ * @typeParam T - Форма данных модели.
58
+ * @param model - Модель, по которой идут прогоны.
59
+ * @param validation - Правила; `undefined` — валидации у формы нет.
60
+ * @returns Бандл валидации либо `undefined`, если правила не заданы.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const v = buildValidation(model, {
65
+ * steps: { loan: loanRules, applicant: applicantRules, confirm: null },
66
+ * extras: crossFieldRules,
67
+ * strategy: 'blur',
68
+ * });
69
+ * await v!.validateStep(1); // правила шага `loan`
70
+ * await v!.validateAll(); // все шаги + extras
71
+ * ```
72
+ */
73
+ export declare function buildValidation<T>(model: FormModel<T>, validation?: FormValidation<T> | ValidationSchema<T>): FormValidationBundle<T> | undefined;
@@ -23,9 +23,15 @@ export interface FormValidationController {
23
23
  * `afterFirstSubmit` в live-фазу. Возвращает `false`, если есть блокирующие ошибки.
24
24
  */
25
25
  validate(): Promise<boolean>;
26
- /** Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR. */
26
+ /**
27
+ * Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR.
28
+ *
29
+ * После {@link FormValidationController.dispose} контроллер армируется ЗАНОВО: React монтирует
30
+ * компоненты повторно (StrictMode в разработке, remount по роуту), и «одноразовый» контроллер
31
+ * после такого цикла молча оставался бы без живой валидации.
32
+ */
27
33
  start(): () => void;
28
- /** Снять подписки/таймеры. Идемпотентно. */
34
+ /** Снять подписки/таймеры. Идемпотентно; после него `start()` снова армирует стратегию. */
29
35
  dispose(): void;
30
36
  /** Идёт ли прогон (submit или live) — снапшот. */
31
37
  readonly isValidating: boolean;