@reformer/core 9.0.0-beta.1 → 9.0.0-beta.2
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/README.md +52 -57
- package/dist/behaviors-Br4Im38V.js +263 -0
- package/dist/behaviors.js +146 -146
- package/dist/core/model/behaviors.d.ts +15 -0
- package/dist/core/nodes/group-node.d.ts +32 -2
- package/dist/core/nodes/model-array-node.d.ts +8 -1
- package/dist/core/types/deep-schema.d.ts +23 -1
- package/dist/core/types/form-proxy.d.ts +26 -1
- package/dist/core/types/validation-schema.d.ts +18 -8
- package/dist/core/utils/derived-registry.d.ts +29 -0
- package/dist/core/utils/form-submitter.d.ts +6 -1
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/safe-effect.d.ts +27 -7
- package/dist/core/validation/validators/required.d.ts +2 -1
- package/dist/hooks/types.d.ts +30 -0
- package/dist/hooks/useSignalSubscription.d.ts +8 -1
- package/dist/index.js +766 -758
- package/dist/validators/pattern.js +7 -6
- package/dist/validators/required.js +5 -9
- package/llms.txt +149 -22
- package/package.json +1 -1
- package/dist/behaviors-O9a1Djj9.js +0 -147
|
@@ -35,27 +35,37 @@ import { FormModel } from '../model/types';
|
|
|
35
35
|
export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
|
|
36
36
|
/**
|
|
37
37
|
* Чистый асинхронный валидатор поля. Та же тройка аргументов `(value, scope, root)`, что у
|
|
38
|
-
* {@link Validator}, но возвращает `Promise`.
|
|
39
|
-
*
|
|
38
|
+
* {@link Validator}, но возвращает `Promise`.
|
|
39
|
+
*
|
|
40
|
+
* @deprecated Осиротевший остаток удалённого оператора `validateAsync` (Ф7). Рантаймом не
|
|
41
|
+
* потребляется — живой async-путь узла поля использует `AsyncValidatorFn` `(value, { signal })`.
|
|
42
|
+
* Экспортируется только ради обратной совместимости.
|
|
40
43
|
*/
|
|
41
44
|
export type AsyncValidator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => Promise<ValidationError | null>;
|
|
42
45
|
/**
|
|
43
|
-
*
|
|
46
|
+
* Функция-предикат `(value) => boolean`.
|
|
47
|
+
*
|
|
48
|
+
* @deprecated Осиротевший остаток удалённого оператора `applyWhen` (Ф7). Рантаймом не
|
|
49
|
+
* потребляется; экспортируется только ради обратной совместимости.
|
|
44
50
|
*/
|
|
45
51
|
export type ConditionFn<T> = (value: T) => boolean;
|
|
46
52
|
/**
|
|
47
|
-
* Опции
|
|
53
|
+
* Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)
|
|
54
|
+
* аргументом в фабрику и попадают в возвращаемую {@link ValidationError}.
|
|
48
55
|
*/
|
|
49
56
|
export interface ValidateOptions {
|
|
50
|
-
/**
|
|
57
|
+
/** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
|
|
58
|
+
* резолвится из `code` (см. резолвер сообщений в `@reformer/cdk`). */
|
|
51
59
|
message?: string;
|
|
52
|
-
/** Параметры ошибки */
|
|
60
|
+
/** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
|
|
53
61
|
params?: Record<string, FormValue>;
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
56
|
-
*
|
|
64
|
+
* @deprecated Осиротевший остаток удалённого оператора `validateAsync` (Ф7): опция `debounce`
|
|
65
|
+
* подключалась тем оператором, которого больше нет. Рантаймом не потребляется; экспортируется
|
|
66
|
+
* только ради обратной совместимости.
|
|
57
67
|
*/
|
|
58
68
|
export interface ValidateAsyncOptions extends ValidateOptions {
|
|
59
|
-
/** Задержка перед выполнением валидации (в мс) */
|
|
69
|
+
/** Задержка перед выполнением валидации (в мс). */
|
|
60
70
|
debounce?: number;
|
|
61
71
|
}
|
|
@@ -6,6 +6,9 @@ import { Signal } from '@preact/signals-core';
|
|
|
6
6
|
* bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
|
|
7
7
|
* значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
|
|
8
8
|
*
|
|
9
|
+
* Идемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,
|
|
10
|
+
* пишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.
|
|
11
|
+
*
|
|
9
12
|
* @param signal - Сигнал значения из {@link FormModel}, которым владеет compute
|
|
10
13
|
*
|
|
11
14
|
* @example
|
|
@@ -17,9 +20,35 @@ import { Signal } from '@preact/signals-core';
|
|
|
17
20
|
* ```
|
|
18
21
|
*
|
|
19
22
|
* @see {@link isDerived} - проверка пометки
|
|
23
|
+
* @see {@link unmarkDerived} - снять пометку при dispose оператора
|
|
20
24
|
* @group Utilities
|
|
21
25
|
*/
|
|
22
26
|
export declare function markDerived(signal: Signal<any>): void;
|
|
27
|
+
/**
|
|
28
|
+
* Снять пометку производного сигнала (обратная операция к {@link markDerived}).
|
|
29
|
+
*
|
|
30
|
+
* Вызывается из очистки (`onDispose`) оператора `compute`/`computeFrom`, когда поведение снимается,
|
|
31
|
+
* но модель/сигнал продолжают жить (динамическая перекоммутация: удалить под-схему → сохранить модель
|
|
32
|
+
* → bulk-set). Без этого сигнал остаётся помеченным навсегда, и bulk-сеттеры навсегда пропускают
|
|
33
|
+
* поле, которое больше ничем не вычисляется.
|
|
34
|
+
*
|
|
35
|
+
* Учитывает счётчик ссылок: пометка снимается только когда снят последний владелец. Вызов на
|
|
36
|
+
* непомеченном сигнале — безопасный no-op.
|
|
37
|
+
*
|
|
38
|
+
* @param signal - Сигнал значения из {@link FormModel}, ранее помеченный {@link markDerived}
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { markDerived, unmarkDerived } from '@reformer/core';
|
|
43
|
+
*
|
|
44
|
+
* markDerived(model.$.total);
|
|
45
|
+
* onDispose(() => unmarkDerived(model.$.total)); // при снятии compute снова разрешаем bulk-set
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @see {@link markDerived} - пометить сигнал производным
|
|
49
|
+
* @group Utilities
|
|
50
|
+
*/
|
|
51
|
+
export declare function unmarkDerived(signal: Signal<any>): void;
|
|
23
52
|
/**
|
|
24
53
|
* Производный ли сигнал (помечен через {@link markDerived}).
|
|
25
54
|
*
|
|
@@ -70,7 +70,12 @@ export declare class FormSubmitter<T extends object> {
|
|
|
70
70
|
*
|
|
71
71
|
* @param onSubmit - Callback для отправки данных
|
|
72
72
|
* @param options - Опции submit
|
|
73
|
-
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
73
|
+
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Возвращаемый `null` неоднозначен: он сигналит и провал валидации, и легитимный `null`,
|
|
77
|
+
* который вернул `onSubmit` (или void-обработчик). Чтобы различать эти случаи, используйте
|
|
78
|
+
* {@link submitWithResult} — он отдаёт явный флаг `success`.
|
|
74
79
|
*
|
|
75
80
|
* @example
|
|
76
81
|
* ```typescript
|
|
@@ -9,7 +9,7 @@ export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
|
9
9
|
export { createForm, createFormFromModel } from './create-form';
|
|
10
10
|
export type { CreateFormFromModelArgs } from './create-form';
|
|
11
11
|
export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
|
|
12
|
-
export { markDerived, isDerived } from './derived-registry';
|
|
12
|
+
export { markDerived, isDerived, unmarkDerived } from './derived-registry';
|
|
13
13
|
export { uniqueId } from './unique-id';
|
|
14
14
|
export { safeCallback, runOutsideEffect, safeDebouncedCallback } from './safe-effect';
|
|
15
15
|
export { FormSubmitter } from './form-submitter';
|
|
@@ -4,14 +4,28 @@
|
|
|
4
4
|
* Эти утилиты решают проблему "Cycle detected" в @preact/signals-core,
|
|
5
5
|
* которая возникает при модификации сигналов внутри effect.
|
|
6
6
|
*
|
|
7
|
+
* Отложенная запись ({@link runOutsideEffect}) откладывается на микротаск и при этом:
|
|
8
|
+
* - маршрутизирует синхронные throws и async-rejection через {@link FormErrorHandler} вместо того,
|
|
9
|
+
* чтобы они всплывали неперехваченными в точке микротаска / тонули как unhandledRejection;
|
|
10
|
+
* - возвращает отменитель (canceller) для liveness — вызывающий может отменить ещё не выполненную
|
|
11
|
+
* запись при dispose, чтобы она не сработала против снесённого узла/формы.
|
|
12
|
+
*
|
|
13
|
+
* NB: намеренно НЕ оборачиваем записи в `batch()`. Реципрокные guard'ы слоя данных (например
|
|
14
|
+
* `syncFields` в {@link module:core/model/behaviors}) держат флаг «идёт синхронизация» и сбрасывают его
|
|
15
|
+
* в `finally` ПОСЛЕ записи, полагаясь на то, что встречный эффект флашится СИНХРОННО во время записи
|
|
16
|
+
* (вне batch). `batch()` отложил бы встречный флаш за пределы сброса флага и сломал бы эту защиту
|
|
17
|
+
* (сходимость осталась бы за peek-guard'ом, но появились бы лишние круги). Коалесинг записей в один
|
|
18
|
+
* batch — законная оптимизация, но она требует, чтобы эти guard'ы стали batch-aware, поэтому делается
|
|
19
|
+
* не здесь. См. также `aggregateInto`, где коалесинг реализован на уровне самого оператора.
|
|
20
|
+
*
|
|
7
21
|
* @group Utils
|
|
8
22
|
* @module utils/safe-effect
|
|
9
23
|
*/
|
|
10
24
|
/**
|
|
11
25
|
* Создает callback, который выполняется вне контекста effect
|
|
12
26
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
27
|
+
* Откладывает вызов через {@link runOutsideEffect} — то есть получает маршрутизацию ошибок и
|
|
28
|
+
* обработку async-rejection «бесплатно».
|
|
15
29
|
*
|
|
16
30
|
* @param callback - Функция для выполнения
|
|
17
31
|
* @returns Обёрнутая функция, безопасная для вызова внутри effect
|
|
@@ -33,27 +47,33 @@
|
|
|
33
47
|
*/
|
|
34
48
|
export declare function safeCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void | Promise<void>): (...args: TArgs) => void;
|
|
35
49
|
/**
|
|
36
|
-
* Выполняет функцию вне контекста effect
|
|
50
|
+
* Выполняет функцию вне контекста effect (отложенная запись на микротаск).
|
|
37
51
|
*
|
|
38
|
-
*
|
|
52
|
+
* Сбои fn (синхронный throw и async-rejection) маршрутизируются через {@link FormErrorHandler}, а не
|
|
53
|
+
* всплывают неперехваченными. Возвращает отменитель: вызов до срабатывания микротаска отменяет запись
|
|
54
|
+
* (liveness-охрана — не писать против снесённого узла при dispose в том же тике).
|
|
39
55
|
*
|
|
40
56
|
* @param fn - Функция для выполнения
|
|
57
|
+
* @returns Отменитель ещё не выполненной записи (no-op, если запись уже выполнена)
|
|
41
58
|
*
|
|
42
59
|
* @example
|
|
43
60
|
* ```typescript
|
|
44
61
|
* effect(() => {
|
|
45
62
|
* const value = signal.value;
|
|
46
|
-
* runOutsideEffect(() => {
|
|
63
|
+
* const cancel = runOutsideEffect(() => {
|
|
47
64
|
* otherSignal.value = transform(value);
|
|
48
65
|
* });
|
|
66
|
+
* onDispose(cancel); // отменить ожидающую запись, если узел снесут в этом же тике
|
|
49
67
|
* });
|
|
50
68
|
* ```
|
|
51
69
|
*/
|
|
52
|
-
export declare function runOutsideEffect(fn: () => void | Promise<void>): void;
|
|
70
|
+
export declare function runOutsideEffect(fn: () => void | Promise<void>): () => void;
|
|
53
71
|
/**
|
|
54
72
|
* Создает версию callback с поддержкой debounce, безопасную для effect
|
|
55
73
|
*
|
|
56
|
-
*
|
|
74
|
+
* `withDebounce` уже откладывает вызов (таймер) — этого достаточно для выхода из effect-контекста,
|
|
75
|
+
* поэтому дополнительного `queueMicrotask` нет (устранён двойной defer). Сбои callback (в т.ч.
|
|
76
|
+
* async-rejection) маршрутизируются через {@link FormErrorHandler}.
|
|
57
77
|
*
|
|
58
78
|
* @param callback - Функция для выполнения
|
|
59
79
|
* @param withDebounce - Функция debounce обёртки из BehaviorRegistry
|
|
@@ -4,7 +4,8 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
4
4
|
*
|
|
5
5
|
* Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
|
|
6
6
|
*
|
|
7
|
-
* Пустыми считаются: `null`, `undefined`, `''` (пустая строка)
|
|
7
|
+
* Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
|
|
8
|
+
* обязательный multi-select / FormArray без выбранных элементов).
|
|
8
9
|
* Для boolean полей требуется значение `true`.
|
|
9
10
|
*
|
|
10
11
|
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
package/dist/hooks/types.d.ts
CHANGED
|
@@ -191,6 +191,20 @@ export interface FieldControlState<T> {
|
|
|
191
191
|
* ```
|
|
192
192
|
*/
|
|
193
193
|
componentProps: Record<string, unknown>;
|
|
194
|
+
/**
|
|
195
|
+
* Флаг изменения поля.
|
|
196
|
+
* `true` когда значение поля отличается от начального.
|
|
197
|
+
*
|
|
198
|
+
* Реактивный аналог `control.dirty` — паритет с {@link ArrayControlState.dirty}.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* const { dirty } = useFormControl(field);
|
|
203
|
+
*
|
|
204
|
+
* return dirty ? <span className="badge">Изменено</span> : null;
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
dirty: boolean;
|
|
194
208
|
}
|
|
195
209
|
/**
|
|
196
210
|
* Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.
|
|
@@ -325,4 +339,20 @@ export interface ArrayControlState<T> {
|
|
|
325
339
|
* ```
|
|
326
340
|
*/
|
|
327
341
|
dirty: boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Флаг отключения массива.
|
|
344
|
+
* `true` когда массив отключён (`ArrayNode.disable()`), в том числе через
|
|
345
|
+
* распространение disable от родительской группы.
|
|
346
|
+
*
|
|
347
|
+
* Используйте для отключения UI-действий добавления/удаления, когда массив
|
|
348
|
+
* структурно неизменяем.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```tsx
|
|
352
|
+
* const { disabled } = useFormControl(itemsArray);
|
|
353
|
+
*
|
|
354
|
+
* return <button disabled={disabled} onClick={() => control.push()}>Add</button>;
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
disabled: boolean;
|
|
328
358
|
}
|
|
@@ -14,4 +14,11 @@ export interface SignalConfig<K extends string> {
|
|
|
14
14
|
* Хук для подписки на набор сигналов с кешированием
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
|
-
export declare function useSignalSubscription<TSignals extends SignalMap, TSnapshot extends ExtractSignalValues<TSignals>>(signals: TSignals, configs: SignalConfig<Extract<keyof TSignals, string>>[], buildSnapshot: (values: ExtractSignalValues<TSignals>) => TSnapshot
|
|
17
|
+
export declare function useSignalSubscription<TSignals extends SignalMap, TSnapshot extends ExtractSignalValues<TSignals>>(signals: TSignals, configs: SignalConfig<Extract<keyof TSignals, string>>[], buildSnapshot: (values: ExtractSignalValues<TSignals>) => TSnapshot,
|
|
18
|
+
/**
|
|
19
|
+
* Стабильная идентичность подписки (обычно сам узел-контрол). subscribe/getSnapshot
|
|
20
|
+
* пересоздаются ТОЛЬКО при её смене, а не на каждом рендере — как у useFormControlValue,
|
|
21
|
+
* который ключует по `[control]`. Это убирает per-render churn (dispose+recreate preact-effect),
|
|
22
|
+
* сохраняя реактивность при подмене контрола на другой узел.
|
|
23
|
+
*/
|
|
24
|
+
subscriptionKey?: unknown): TSnapshot;
|