@reformer/core 11.0.0-beta.2 → 11.0.0-beta.4
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.d.ts +1 -1
- package/dist/behaviors.js +37 -37
- package/dist/create-model-Dxh9cfLp.js +97 -0
- package/dist/form/behaviors/collections.d.ts +50 -0
- package/dist/form/behaviors/context.d.ts +23 -0
- package/dist/form/behaviors/index.d.ts +28 -0
- package/dist/form/behaviors/internals.d.ts +26 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
- package/dist/form/behaviors/operators.d.ts +51 -0
- package/dist/form/behaviors/types.d.ts +17 -0
- package/dist/form/create-core-form.d.ts +70 -0
- package/dist/form/create-form.d.ts +1 -1
- package/dist/form/index.d.ts +12 -12
- package/dist/form/nodes/model-array-node.d.ts +1 -1
- package/dist/form/types/contracts.d.ts +9 -6
- package/dist/form/types/validation-schema.d.ts +1 -1
- package/dist/form/validation/config.d.ts +73 -0
- package/dist/form/validation/context.d.ts +27 -0
- package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
- package/dist/form/validation/index.d.ts +23 -28
- package/dist/form/validation/operators.d.ts +42 -0
- package/dist/form/validation/run.d.ts +45 -0
- package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +10 -4
- package/dist/form/validation/types.d.ts +28 -0
- package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/email.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/file-type.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/future-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/index.d.ts +6 -4
- package/dist/form/{validation/validators → validators}/integer.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/is-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/is-number.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-age.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-files.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-length.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-age.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-files.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-length.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/multiple-of.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/non-negative.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/non-zero.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/past-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/pattern.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/phone.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/required.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/url.d.ts +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +708 -479
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
- package/dist/{state → model}/index.d.ts +10 -11
- package/dist/model/model-nodes.d.ts +60 -0
- package/dist/model/model-signals-proxy.d.ts +32 -0
- package/dist/model/model-value-proxy.d.ts +3 -0
- package/dist/{state → model}/safe-effect.d.ts +1 -1
- package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +21 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +26 -0
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
- package/dist/platforms/react/index.d.ts +23 -0
- package/dist/signals.d.ts +1 -1
- package/dist/strategy-DA3JwxsR.js +170 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +10 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators.d.ts +1 -1
- package/docs/llms/02-quick-start.md +14 -7
- package/docs/llms/15-project-structure.md +12 -8
- package/llms.txt +329 -53
- package/package.json +4 -4
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validators.d.ts +0 -11
- package/dist/form-model-DhW2HZOB.js +0 -323
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
- /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
- /package/dist/{state → model}/behaviors-value.d.ts +0 -0
- /package/dist/{state → model}/derived-registry.d.ts +0 -0
- /package/dist/{state → model}/types.d.ts +0 -0
- /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
- /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { FormModel } from '../../model/types';
|
|
3
|
+
import { ValidationSchema } from './types';
|
|
4
|
+
import { FormValidationController, ValidationStrategyKind } from './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;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FormModel, PathAwareSignal, ValidationError } from '../../index';
|
|
2
|
+
/** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
|
|
3
|
+
export interface VContext {
|
|
4
|
+
/** Модель текущего scope — источник снапшота для `cross` и модель для `apply`. */
|
|
5
|
+
model: FormModel<unknown>;
|
|
6
|
+
/** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
|
|
7
|
+
errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
|
|
8
|
+
/** Незавершённые async-правила (раннер их дожидается). */
|
|
9
|
+
pending: Promise<void>[];
|
|
10
|
+
/** Стек активных условий `validateWhen` (все должны быть истинны, чтобы правило сработало). */
|
|
11
|
+
whenStack: Array<() => boolean>;
|
|
12
|
+
/** Отмена устаревшего прогона: прокидывается в async-правила, гасит роутинг после await. */
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Открыть ambient-окно на время `fn` и закрыть его в `finally`.
|
|
17
|
+
*
|
|
18
|
+
* Единственный способ установить контекст: прямое присваивание `current` из другого модуля
|
|
19
|
+
* невозможно. Сохранение/восстановление `prev` делает вложенный прогон безопасным.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runWithContext<R>(ctx: VContext, fn: () => R): R;
|
|
22
|
+
/** Контекст активного прогона либо понятная ошибка, если оператор вызван вне схемы. */
|
|
23
|
+
export declare function requireCtx(op: string): VContext;
|
|
24
|
+
/** «Тронуть» сигнал: гарантирует его наличие в `errors` (даже пустым) → он попадёт в очистку. */
|
|
25
|
+
export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationError[];
|
|
26
|
+
/** Активна ли текущая ветка (все `validateWhen`-условия истинны). */
|
|
27
|
+
export declare const gated: (ctx: VContext) => boolean;
|
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export
|
|
24
|
-
export { fileType } from './validators/file-type';
|
|
25
|
-
export { maxFiles } from './validators/max-files';
|
|
26
|
-
export { minFiles } from './validators/min-files';
|
|
27
|
-
export { maxTotalFileSize } from './validators/max-total-file-size';
|
|
28
|
-
export { isFileLike, matchesFileAccept, toFileArray, type FileLike } from './validators/file-utils';
|
|
1
|
+
/**
|
|
2
|
+
* Декларативный контракт СХЕМЫ ВАЛИДАЦИИ — `@reformer/core/validation`.
|
|
3
|
+
*
|
|
4
|
+
* Схема валидации — обычная функция `(ctx: { model }) => void`. Внутри вызываются свободные операторы
|
|
5
|
+
* (`validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`), которые САМИ пишут ошибки в ambient-сток
|
|
6
|
+
* текущего прогона — автор не видит ни коллектора, ни `getNodeForSignal`, ни `.push`. Внешний раннер
|
|
7
|
+
* {@link validateModel} открывает ambient-окно на время СИНХРОННОГО прогона схемы, дожидается async-правил
|
|
8
|
+
* и разносит ошибки по нодам формы (`getNodeForSignal(sig).setErrors(...)`), гася поля, ставшие валидными.
|
|
9
|
+
*
|
|
10
|
+
* Зеркалит контракт поведения (`@reformer/core/behaviors`): тот же ambient-стиль, но отдельный слой —
|
|
11
|
+
* валидация НЕ реактивна (прогон по требованию: submit/шаг), поведение — реактивно (живые подписки).
|
|
12
|
+
*
|
|
13
|
+
* Модуль разложен по зонам ответственности: `./types` — контракт правил и схемы, `./context` —
|
|
14
|
+
* ambient-сток прогона, `./operators` — операторы схемы, `./run` — раннер, `./strategy` — КОГДА
|
|
15
|
+
* запускать (реэкспортируется отсюда, тот же сабпат).
|
|
16
|
+
*
|
|
17
|
+
* @group Validation
|
|
18
|
+
* @module form/validation
|
|
19
|
+
*/
|
|
20
|
+
export type { Rule, AsyncRule, ValidationSchema } from './types';
|
|
21
|
+
export { validate, validateAsync, validateWhen, cross, each, apply } from './operators';
|
|
22
|
+
export { defineValidationSchema, validateModel } from './run';
|
|
23
|
+
export * from './strategy';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FormModel, ModelArray, PathAwareSignal, ValidationError } from '../../index';
|
|
2
|
+
import { AsyncRule, Rule, ValidationSchema } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Синхронные правила поля.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).
|
|
14
|
+
* Раннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* validateAsync(model.$.username, [async (v, { signal }) => {
|
|
19
|
+
* const r = await fetch(`/api/free?u=${v}`, { signal });
|
|
20
|
+
* return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };
|
|
21
|
+
* }]);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rules: AsyncRule<TField>[]): void;
|
|
25
|
+
/**
|
|
26
|
+
* Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
|
|
27
|
+
* ГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateWhen(cond: () => boolean, cb: () => void): void;
|
|
30
|
+
/**
|
|
31
|
+
* Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.
|
|
32
|
+
* Для элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),
|
|
33
|
+
* т.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.
|
|
34
|
+
*/
|
|
35
|
+
export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
|
|
36
|
+
/**
|
|
37
|
+
* Применить под-правила к КАЖДОМУ элементу текущего массива модели.
|
|
38
|
+
* `U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.
|
|
39
|
+
*/
|
|
40
|
+
export declare function each<U extends object>(arr: ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
|
|
41
|
+
/** Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку. */
|
|
42
|
+
export declare function apply<T>(...schemas: ValidationSchema<T>[]): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FormModel } from '../../index';
|
|
2
|
+
import { ValidationSchema } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* export const step1 = defineValidationSchema<LoanForm>(({ model }) => {
|
|
9
|
+
* validate(model.$.loanAmount, [required(), min(50000)]);
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineValidationSchema<T>(schema: ValidationSchema<T>): ValidationSchema<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма). Открывает ambient-окно на время
|
|
16
|
+
* синхронного прогона `schema`, дожидается async-правил, разносит ошибки по нодам формы и гасит
|
|
17
|
+
* поля, ставшие валидными. Возвращает `true`, если нет блокирующих ошибок (`severity:'warning'` не блокирует).
|
|
18
|
+
*
|
|
19
|
+
* Гашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)` не мешают
|
|
20
|
+
* друг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema)) отменяется через `AbortSignal`
|
|
21
|
+
* и возвращает `false` (**fail-closed** — отменённому результату нельзя доверять для submit).
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности `schema`.
|
|
24
|
+
* Инлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ прогон без дедупликации —
|
|
25
|
+
* держите схемы в `const` / `defineValidationSchema`.
|
|
26
|
+
*
|
|
27
|
+
* `options.touch` (§6): по завершении помечает `touched` ИМЕННО поля, которые схема проверяла
|
|
28
|
+
* (не всё поддерево). Ошибки становятся видимыми (`shouldShowError = invalid && (touched||dirty)`)
|
|
29
|
+
* без ручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается
|
|
30
|
+
* «тронутым» до ввода. Валидные проверенные поля тоже метятся — это безвредно (ошибка не покажется).
|
|
31
|
+
*
|
|
32
|
+
* @param model - Модель данных формы.
|
|
33
|
+
* @param schema - Схема валидации (стабильная ссылка).
|
|
34
|
+
* @param options - `{ touch }` — пометить провалидированные поля `touched` для показа ошибок.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const ok = await validateModel(model, step2Validation); // один шаг
|
|
39
|
+
* const all = await validateModel(model, formValidation); // вся форма
|
|
40
|
+
* const stepOk = await validateModel(model, step2Validation, { touch: true }); // + показать ошибки шага
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateModel<T>(model: FormModel<T>, schema: ValidationSchema<T>, options?: {
|
|
44
|
+
touch?: boolean;
|
|
45
|
+
}): Promise<boolean>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
-
import { FormModel } from '
|
|
3
|
-
import { ValidationSchema } from './
|
|
2
|
+
import { FormModel } from '../../index';
|
|
3
|
+
import { ValidationSchema } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Когда запускается schema-валидация:
|
|
6
6
|
* - `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке);
|
|
@@ -23,9 +23,15 @@ export interface FormValidationController {
|
|
|
23
23
|
* `afterFirstSubmit` в live-фазу. Возвращает `false`, если есть блокирующие ошибки.
|
|
24
24
|
*/
|
|
25
25
|
validate(): Promise<boolean>;
|
|
26
|
-
/**
|
|
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;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FormModel, ValidationError } from '../../index';
|
|
2
|
+
/**
|
|
3
|
+
* Внутренний callable-вид правила: value-only фабрики игнорируют 2-й/3-й аргумент.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type CallableRule = (value: unknown, model: unknown, root: unknown) => ValidationError | null;
|
|
8
|
+
/**
|
|
9
|
+
* Синхронное правило поля типа `TField`. Проверяется ТОЛЬКО значение (`value`).
|
|
10
|
+
*
|
|
11
|
+
* Позиционные `scope`/`root` помечены `never` намеренно: так встроенные value-only фабрики
|
|
12
|
+
* (`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
|
|
13
|
+
* ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
|
|
14
|
+
* (`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
|
|
15
|
+
* На вызове раннер приводит правило к callable и передаёт `(value, model, model)`.
|
|
16
|
+
*/
|
|
17
|
+
export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
|
|
18
|
+
/**
|
|
19
|
+
* Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный
|
|
20
|
+
* прогон той же схемы отменяет предыдущий). Сетевой сбой не должен блокировать — ловите и возвращайте `null`.
|
|
21
|
+
*/
|
|
22
|
+
export type AsyncRule<TField> = (value: TField, ctx: {
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}) => Promise<ValidationError | null>;
|
|
25
|
+
/** Схема валидации — обычная функция над (под)моделью. First-class значение (можно `apply`/тестировать/переиспользовать). */
|
|
26
|
+
export type ValidationSchema<T> = (ctx: {
|
|
27
|
+
model: FormModel<T>;
|
|
28
|
+
}) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Точка входа сабпата `@reformer/core/validators` — все фабрики правил одним импортом.
|
|
3
3
|
*
|
|
4
|
-
* Все примитивы — фабрики, возвращающие чистый Validator<TForm, TField
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Все примитивы — фабрики, возвращающие чистый `Validator<TForm, TField>`; передаются в
|
|
5
|
+
* массив `validators` поля схемы формы: `validators: [required(), min(18)]`.
|
|
6
|
+
*
|
|
7
|
+
* Гранулярные сабпаты (`@reformer/core/validators/required` и ещё 20) собираются отдельными
|
|
8
|
+
* entry для tree-shaking; этот модуль отдаёт полный набор для случаев, когда правил много.
|
|
7
9
|
*/
|
|
8
10
|
export { required } from './required';
|
|
9
11
|
export { min } from './min';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Validator, ValidateOptions } from '
|
|
1
|
+
import { Validator, ValidateOptions } from '../types/validation-schema';
|
|
2
2
|
/**
|
|
3
3
|
* Опции валидатора {@link url}. Расширяют {@link ValidateOptions} (`message`, `params`)
|
|
4
4
|
* настройками проверки протокола.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Публичный barrel `@reformer/core` — зонтик над
|
|
2
|
+
* Публичный barrel `@reformer/core` — зонтик над слоями `model`, `form` и `platforms/react`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `model` — реактивная модель данных (сабпат `@reformer/core/model`); `form` — узлы, поведение и
|
|
5
|
+
* валидация поверх её сигналов; `platforms/react` — биндинги в React, единственный слой с
|
|
6
|
+
* рантайм-зависимостью от `react`. Состав экспортов зонтика не меняется при реорганизациях:
|
|
7
|
+
* реализация разложена по слоям, а barrel по-прежнему отдаёт единую поверхность.
|
|
6
8
|
*/
|
|
7
9
|
export * from './form/types/index';
|
|
8
10
|
export * from './form/factories/index';
|
|
9
|
-
export * from './
|
|
11
|
+
export * from './model/index';
|
|
10
12
|
export * from './form/index';
|
|
13
|
+
export * from './platforms/react/index';
|
|
11
14
|
export { CORE_RUNTIME_TOKEN } from './runtime-token';
|