@reformer/core 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/behaviors/compute-from.d.ts +2 -0
- package/dist/behaviors/compute-from.js +31 -0
- package/dist/behaviors/copy-from.d.ts +2 -0
- package/dist/behaviors/copy-from.js +29 -0
- package/dist/behaviors/enable-when.d.ts +2 -0
- package/dist/behaviors/enable-when.js +25 -0
- package/dist/behaviors/reset-when.d.ts +2 -0
- package/dist/behaviors/reset-when.js +24 -0
- package/dist/behaviors/revalidate-when.d.ts +2 -0
- package/dist/behaviors/revalidate-when.js +18 -0
- package/dist/behaviors/sync-fields.d.ts +2 -0
- package/dist/behaviors/sync-fields.js +41 -0
- package/dist/behaviors/transform-value.d.ts +2 -0
- package/dist/behaviors/transform-value.js +45 -0
- package/dist/behaviors/watch-field.d.ts +2 -0
- package/dist/behaviors/watch-field.js +21 -0
- package/dist/behaviors.d.ts +6 -0
- package/dist/behaviors.js +29 -0
- package/dist/core/behavior/behavior-context.d.ts +47 -0
- package/dist/core/behavior/behavior-registry.d.ts +85 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +70 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +56 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +118 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +64 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +53 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +48 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +192 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +80 -0
- package/dist/core/behavior/compose-behavior.d.ts +96 -0
- package/dist/core/behavior/index.d.ts +11 -0
- package/dist/core/behavior/types.d.ts +146 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/node-factory.d.ts +186 -0
- package/dist/core/nodes/array-node.d.ts +328 -0
- package/dist/core/nodes/field-node.d.ts +294 -0
- package/dist/core/nodes/form-node.d.ts +340 -0
- package/dist/core/nodes/group-node.d.ts +319 -0
- package/dist/core/types/deep-schema.d.ts +68 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/form-context.d.ts +89 -0
- package/dist/core/types/form-proxy.d.ts +105 -0
- package/dist/core/types/index.d.ts +209 -0
- package/dist/core/types/validation-schema.d.ts +95 -0
- package/dist/core/utils/abstract-registry.d.ts +74 -0
- package/dist/core/utils/aggregate-signals.d.ts +71 -0
- package/dist/core/utils/create-form.d.ts +44 -0
- package/dist/core/utils/error-handler.d.ts +163 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path.d.ts +60 -0
- package/dist/core/utils/form-observer.d.ts +176 -0
- package/dist/core/utils/form-proxy-builder.d.ts +25 -0
- package/dist/core/utils/form-submitter.d.ts +121 -0
- package/dist/core/utils/index.d.ts +22 -0
- package/dist/core/utils/registry-helpers.d.ts +43 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/safe-effect.d.ts +73 -0
- package/dist/core/utils/status-machine.d.ts +153 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/type-guards.d.ts +98 -0
- package/dist/core/utils/unique-id.d.ts +53 -0
- package/dist/core/validation/core/apply-when.d.ts +22 -0
- package/dist/core/validation/core/apply.d.ts +52 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/validate-async.d.ts +36 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate.d.ts +26 -0
- package/dist/core/validation/index.d.ts +27 -0
- package/dist/core/validation/validate-form.d.ts +48 -0
- package/dist/core/validation/validation-applicator.d.ts +70 -0
- package/dist/core/validation/validation-context.d.ts +86 -0
- package/dist/core/validation/validation-registry.d.ts +142 -0
- package/dist/core/validation/validators/array-validators.d.ts +53 -0
- package/dist/core/validation/validators/date-utils.d.ts +26 -0
- package/dist/core/validation/validators/email.d.ts +37 -0
- package/dist/core/validation/validators/future-date.d.ts +35 -0
- package/dist/core/validation/validators/index.d.ts +20 -0
- package/dist/core/validation/validators/is-date.d.ts +36 -0
- package/dist/core/validation/validators/max-age.d.ts +36 -0
- package/dist/core/validation/validators/max-date.d.ts +36 -0
- package/dist/core/validation/validators/max-length.d.ts +38 -0
- package/dist/core/validation/validators/max.d.ts +38 -0
- package/dist/core/validation/validators/min-age.d.ts +36 -0
- package/dist/core/validation/validators/min-date.d.ts +36 -0
- package/dist/core/validation/validators/min-length.d.ts +38 -0
- package/dist/core/validation/validators/min.d.ts +38 -0
- package/dist/core/validation/validators/number.d.ts +31 -0
- package/dist/core/validation/validators/past-date.d.ts +35 -0
- package/dist/core/validation/validators/pattern.d.ts +40 -0
- package/dist/core/validation/validators/phone.d.ts +27 -0
- package/dist/core/validation/validators/required.d.ts +41 -0
- package/dist/core/validation/validators/url.d.ts +22 -0
- package/dist/date-utils-xUWFslTj.js +29 -0
- package/dist/field-path-DuKdGcIE.js +66 -0
- package/dist/hooks/types.d.ts +328 -0
- package/dist/hooks/useArrayLength.d.ts +31 -0
- package/dist/hooks/useFormControl.d.ts +24 -0
- package/dist/hooks/useFormControlValue.d.ts +167 -0
- package/dist/hooks/useHiddenCondition.d.ts +25 -0
- package/dist/hooks/useSignalSubscription.d.ts +17 -0
- package/dist/index-D25LsbRm.js +73 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3271 -0
- package/dist/registry-helpers-Bv_BJ1s-.js +615 -0
- package/dist/safe-effect-Dh8uw81c.js +20 -0
- package/dist/validate-C3XiA_zf.js +10 -0
- package/dist/validators/email.d.ts +2 -0
- package/dist/validators/email.js +13 -0
- package/dist/validators/future-date.d.ts +2 -0
- package/dist/validators/future-date.js +20 -0
- package/dist/validators/is-date.d.ts +2 -0
- package/dist/validators/is-date.js +12 -0
- package/dist/validators/max-age.d.ts +2 -0
- package/dist/validators/max-age.js +20 -0
- package/dist/validators/max-date.d.ts +2 -0
- package/dist/validators/max-date.js +20 -0
- package/dist/validators/max-length.d.ts +2 -0
- package/dist/validators/max-length.js +11 -0
- package/dist/validators/max.d.ts +2 -0
- package/dist/validators/max.js +11 -0
- package/dist/validators/min-age.d.ts +2 -0
- package/dist/validators/min-age.js +20 -0
- package/dist/validators/min-date.d.ts +2 -0
- package/dist/validators/min-date.js +20 -0
- package/dist/validators/min-length.d.ts +2 -0
- package/dist/validators/min-length.js +11 -0
- package/dist/validators/min.d.ts +2 -0
- package/dist/validators/min.js +11 -0
- package/dist/validators/number.d.ts +2 -0
- package/dist/validators/number.js +35 -0
- package/dist/validators/past-date.d.ts +2 -0
- package/dist/validators/past-date.js +20 -0
- package/dist/validators/pattern.d.ts +2 -0
- package/dist/validators/pattern.js +11 -0
- package/dist/validators/phone.d.ts +2 -0
- package/dist/validators/phone.js +35 -0
- package/dist/validators/required.d.ts +2 -0
- package/dist/validators/required.js +15 -0
- package/dist/validators/url.d.ts +2 -0
- package/dist/validators/url.js +19 -0
- package/dist/validators-BGsNOgT1.js +207 -0
- package/dist/validators.d.ts +6 -0
- package/dist/validators.js +56 -0
- package/llms.txt +9351 -0
- package/package.json +166 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FieldPathNode } from '../../types';
|
|
2
|
+
import { ComputeFromOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Автоматически вычисляет значение поля на основе других полей
|
|
5
|
+
*
|
|
6
|
+
* @group Behaviors
|
|
7
|
+
* @category Behavior Rules
|
|
8
|
+
*
|
|
9
|
+
* @param sources - Массив полей-зависимостей
|
|
10
|
+
* @param target - Поле для записи результата
|
|
11
|
+
* @param computeFn - Функция вычисления (принимает объект с именами полей)
|
|
12
|
+
* @param options - Опции (`debounce`, `condition`, `trigger`)
|
|
13
|
+
*
|
|
14
|
+
* @example Многополевой расчёт — total = price × quantity
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { computeFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
17
|
+
*
|
|
18
|
+
* interface OrderForm {
|
|
19
|
+
* price: number;
|
|
20
|
+
* quantity: number;
|
|
21
|
+
* total: number;
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* export const orderBehavior: BehaviorSchemaFn<OrderForm> = (path) => {
|
|
25
|
+
* computeFrom(
|
|
26
|
+
* [path.price, path.quantity],
|
|
27
|
+
* path.total,
|
|
28
|
+
* (values) =>
|
|
29
|
+
* (typeof values.price === 'number' ? values.price : 0) *
|
|
30
|
+
* (typeof values.quantity === 'number' ? values.quantity : 0),
|
|
31
|
+
* );
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Edge case — async-like дорогие вычисления с `debounce` и условием
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { computeFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
38
|
+
*
|
|
39
|
+
* interface MortgageForm {
|
|
40
|
+
* loanType: 'mortgage' | 'consumer';
|
|
41
|
+
* loanAmount: number;
|
|
42
|
+
* loanTerm: number;
|
|
43
|
+
* interestRate: number;
|
|
44
|
+
* monthlyPayment: number;
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* function annuity(values: MortgageForm): number {
|
|
48
|
+
* const { loanAmount, loanTerm, interestRate } = values;
|
|
49
|
+
* if (!loanAmount || !loanTerm || !interestRate) return 0;
|
|
50
|
+
* const r = interestRate / 100 / 12;
|
|
51
|
+
* const n = loanTerm;
|
|
52
|
+
* return Math.round((loanAmount * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1));
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* export const mortgageBehavior: BehaviorSchemaFn<MortgageForm> = (path) => {
|
|
56
|
+
* computeFrom(
|
|
57
|
+
* [path.loanAmount, path.loanTerm, path.interestRate],
|
|
58
|
+
* path.monthlyPayment,
|
|
59
|
+
* annuity,
|
|
60
|
+
* {
|
|
61
|
+
* debounce: 300, // не пересчитываем на каждый keystroke
|
|
62
|
+
* condition: (form) => form.loanType === 'mortgage', // считаем только для ипотеки
|
|
63
|
+
* },
|
|
64
|
+
* );
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @see [docs/llms/20-compute-vs-watch.md](../../../../docs/llms/20-compute-vs-watch.md)
|
|
69
|
+
*/
|
|
70
|
+
export declare function computeFrom<TForm, TTarget>(sources: FieldPathNode<TForm, any>[], target: FieldPathNode<TForm, TTarget>, computeFn: (values: TForm) => TTarget, options?: ComputeFromOptions<TForm>): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FieldPathNode } from '../../types';
|
|
2
|
+
import { CopyFromOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Копирует значения из одного поля/группы в другое при выполнении условия
|
|
5
|
+
*
|
|
6
|
+
* @group Behaviors
|
|
7
|
+
* @category Behavior Rules
|
|
8
|
+
*
|
|
9
|
+
* @param source - Откуда копировать
|
|
10
|
+
* @param target - Куда копировать
|
|
11
|
+
* @param options - Опции копирования (`when`, `fields`, `transform`, `debounce`)
|
|
12
|
+
*
|
|
13
|
+
* @example Скаляр → скаляр с условием
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { copyFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
16
|
+
*
|
|
17
|
+
* interface ContactForm {
|
|
18
|
+
* sameEmail: boolean;
|
|
19
|
+
* email: string;
|
|
20
|
+
* emailAdditional: string;
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* export const contactBehavior: BehaviorSchemaFn<ContactForm> = (path) => {
|
|
24
|
+
* copyFrom(path.email, path.emailAdditional, {
|
|
25
|
+
* when: (form) => form.sameEmail === true,
|
|
26
|
+
* });
|
|
27
|
+
* };
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Копирование группы с подмножеством полей и `transform`
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { copyFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
33
|
+
*
|
|
34
|
+
* interface Address { country: string; region: string; city: string; street: string }
|
|
35
|
+
* interface ProfileForm {
|
|
36
|
+
* sameAsRegistration: boolean;
|
|
37
|
+
* registrationAddress: Address;
|
|
38
|
+
* residenceAddress: Address;
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
42
|
+
* copyFrom(path.registrationAddress, path.residenceAddress, {
|
|
43
|
+
* when: (form) => form.sameAsRegistration === true,
|
|
44
|
+
* fields: ['country', 'region', 'city'], // street НЕ копируем
|
|
45
|
+
* transform: (addr) => ({
|
|
46
|
+
* ...addr,
|
|
47
|
+
* country: addr.country.toUpperCase(), // нормализация при копировании
|
|
48
|
+
* }),
|
|
49
|
+
* debounce: 200,
|
|
50
|
+
* });
|
|
51
|
+
* };
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @see [docs/llms/23-copy-from.md](../../../../docs/llms/23-copy-from.md)
|
|
55
|
+
*/
|
|
56
|
+
export declare function copyFrom<TForm, TSource, TTarget>(source: FieldPathNode<TForm, TSource>, target: FieldPathNode<TForm, TTarget>, options?: CopyFromOptions<TSource, TForm>): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { FieldPathNode } from '../../types';
|
|
2
|
+
import { EnableWhenOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Условное включение поля на основе значений других полей
|
|
5
|
+
*
|
|
6
|
+
* @group Behaviors
|
|
7
|
+
* @category Behavior Rules
|
|
8
|
+
*
|
|
9
|
+
* @param field - Поле для включения/выключения
|
|
10
|
+
* @param condition - Функция условия (true = enable, false = disable)
|
|
11
|
+
* @param options - Опции (`resetOnDisable`, `debounce`)
|
|
12
|
+
*
|
|
13
|
+
* @example Базовый сценарий с `resetOnDisable: true`
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { enableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
16
|
+
*
|
|
17
|
+
* interface LoanForm {
|
|
18
|
+
* loanType: 'mortgage' | 'consumer' | 'car';
|
|
19
|
+
* propertyValue: number;
|
|
20
|
+
* initialPayment: number;
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* export const loanBehavior: BehaviorSchemaFn<LoanForm> = (path) => {
|
|
24
|
+
* // Поля ипотеки активны только для loanType === 'mortgage'.
|
|
25
|
+
* // resetOnDisable: true гарантирует чистые initial values при переключении.
|
|
26
|
+
* enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage', {
|
|
27
|
+
* resetOnDisable: true,
|
|
28
|
+
* });
|
|
29
|
+
* enableWhen(path.initialPayment, (form) => form.loanType === 'mortgage', {
|
|
30
|
+
* resetOnDisable: true,
|
|
31
|
+
* });
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Множественные independent условия + cycle prevention
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { enableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
38
|
+
*
|
|
39
|
+
* interface ProfileForm {
|
|
40
|
+
* sameAsRegistration: boolean;
|
|
41
|
+
* employmentStatus: 'employed' | 'selfEmployed' | 'unemployed';
|
|
42
|
+
* residenceAddress: { city: string; street: string };
|
|
43
|
+
* companyName: string;
|
|
44
|
+
* companyInn: string;
|
|
45
|
+
* businessType: string;
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
49
|
+
* // Адрес проживания: enabled, когда НЕ совпадает с регистрационным
|
|
50
|
+
* enableWhen(path.residenceAddress, (form) => form.sameAsRegistration === false, {
|
|
51
|
+
* resetOnDisable: true,
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Поля работодателя: только для employed
|
|
55
|
+
* enableWhen(path.companyName, (form) => form.employmentStatus === 'employed', {
|
|
56
|
+
* resetOnDisable: true,
|
|
57
|
+
* });
|
|
58
|
+
* enableWhen(path.companyInn, (form) => form.employmentStatus === 'employed', {
|
|
59
|
+
* resetOnDisable: true,
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // ИП-поля: только для selfEmployed
|
|
63
|
+
* enableWhen(path.businessType, (form) => form.employmentStatus === 'selfEmployed', {
|
|
64
|
+
* resetOnDisable: true,
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // ВАЖНО: condition не должен читать значение САМОГО поля — иначе цикл.
|
|
68
|
+
* // condition зависит ТОЛЬКО от независимых триггеров (loanType, employmentStatus, ...).
|
|
69
|
+
* };
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @see [docs/llms/22-cycle-detection.md](../../../../docs/llms/22-cycle-detection.md)
|
|
73
|
+
*/
|
|
74
|
+
export declare function enableWhen<TForm>(field: FieldPathNode<TForm, any>, condition: (form: TForm) => boolean, options?: EnableWhenOptions): void;
|
|
75
|
+
/**
|
|
76
|
+
* Условное выключение поля (инверсия enableWhen)
|
|
77
|
+
*
|
|
78
|
+
* @group Behaviors
|
|
79
|
+
* @category Behavior Rules
|
|
80
|
+
*
|
|
81
|
+
* @param field - Поле для выключения
|
|
82
|
+
* @param condition - Функция условия (true = disable, false = enable)
|
|
83
|
+
* @param options - Опции (`resetOnDisable`, `debounce`)
|
|
84
|
+
*
|
|
85
|
+
* @example Базовый сценарий — readonly после подтверждения
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import { disableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
88
|
+
*
|
|
89
|
+
* interface ConfirmForm {
|
|
90
|
+
* isConfirmed: boolean;
|
|
91
|
+
* editableField: string;
|
|
92
|
+
* }
|
|
93
|
+
*
|
|
94
|
+
* export const confirmBehavior: BehaviorSchemaFn<ConfirmForm> = (path) => {
|
|
95
|
+
* // Поле блокируется после установки чекбокса подтверждения
|
|
96
|
+
* disableWhen(path.editableField, (form) => form.isConfirmed === true);
|
|
97
|
+
* // resetOnDisable НЕ ставим — сохраняем введённый текст
|
|
98
|
+
* };
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example С `resetOnDisable` для очистки заблокированного поля
|
|
102
|
+
* ```typescript
|
|
103
|
+
* import { disableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
104
|
+
*
|
|
105
|
+
* interface PromoForm {
|
|
106
|
+
* loanType: 'mortgage' | 'consumer';
|
|
107
|
+
* promoCode: string;
|
|
108
|
+
* }
|
|
109
|
+
*
|
|
110
|
+
* export const promoBehavior: BehaviorSchemaFn<PromoForm> = (path) => {
|
|
111
|
+
* // Промокод недоступен для потребительских кредитов и сбрасывается
|
|
112
|
+
* disableWhen(path.promoCode, (form) => form.loanType === 'consumer', {
|
|
113
|
+
* resetOnDisable: true,
|
|
114
|
+
* });
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function disableWhen<TForm>(field: FieldPathNode<TForm, any>, condition: (form: TForm) => boolean, options?: EnableWhenOptions): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior rules
|
|
3
|
+
*/
|
|
4
|
+
export { copyFrom } from './copy-from';
|
|
5
|
+
export { enableWhen, disableWhen } from './enable-when';
|
|
6
|
+
export { computeFrom } from './compute-from';
|
|
7
|
+
export { watchField } from './watch-field';
|
|
8
|
+
export { revalidateWhen } from './revalidate-when';
|
|
9
|
+
export { syncFields } from './sync-fields';
|
|
10
|
+
export { resetWhen, type ResetWhenOptions } from './reset-when';
|
|
11
|
+
export { transformValue, createTransformer, transformers, type TransformValueOptions, } from './transform-value';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Опции для resetWhen
|
|
4
|
+
*
|
|
5
|
+
* @group Behaviors
|
|
6
|
+
* @category Behavior Types
|
|
7
|
+
*/
|
|
8
|
+
export interface ResetWhenOptions {
|
|
9
|
+
/** Значение для сброса (по умолчанию null) */
|
|
10
|
+
resetValue?: FormValue;
|
|
11
|
+
/** Сбросить только если поле dirty */
|
|
12
|
+
onlyIfDirty?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Условный сброс поля при выполнении условия
|
|
16
|
+
*
|
|
17
|
+
* @group Behaviors
|
|
18
|
+
* @category Behavior Rules
|
|
19
|
+
*
|
|
20
|
+
* @param field - Поле для сброса
|
|
21
|
+
* @param condition - Функция условия (true = reset)
|
|
22
|
+
* @param options - Опции (`resetValue`, `onlyIfDirty`, `debounce`)
|
|
23
|
+
*
|
|
24
|
+
* @example Сброс зависимого поля при смене типа платежа
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { resetWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
27
|
+
*
|
|
28
|
+
* interface CheckoutForm {
|
|
29
|
+
* paymentType: 'card' | 'cash';
|
|
30
|
+
* cardNumber: string;
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* export const checkoutBehavior: BehaviorSchemaFn<CheckoutForm> = (path) => {
|
|
34
|
+
* // Когда выбрано НЕ card — обнуляем номер карты в пустую строку
|
|
35
|
+
* resetWhen(path.cardNumber, (form) => form.paymentType !== 'card', {
|
|
36
|
+
* resetValue: '',
|
|
37
|
+
* });
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example `onlyIfDirty` — не трогаем нетронутые initial значения
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { resetWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
44
|
+
*
|
|
45
|
+
* interface CarForm {
|
|
46
|
+
* loanType: 'mortgage' | 'car' | 'consumer';
|
|
47
|
+
* carPrice: number;
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* export const carBehavior: BehaviorSchemaFn<CarForm> = (path) => {
|
|
51
|
+
* // Если пользователь не вводил carPrice — оставляем default из схемы.
|
|
52
|
+
* // Сбрасываем только если поле dirty (была пользовательская правка).
|
|
53
|
+
* resetWhen(path.carPrice, (form) => form.loanType !== 'car', {
|
|
54
|
+
* resetValue: 0,
|
|
55
|
+
* onlyIfDirty: true,
|
|
56
|
+
* });
|
|
57
|
+
* };
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @see [docs/llms/25-reset-when.md](../../../../docs/llms/25-reset-when.md)
|
|
61
|
+
*/
|
|
62
|
+
export declare function resetWhen<TForm extends FormFields>(field: FieldPathNode<TForm, FormValue>, condition: (form: TForm) => boolean, options?: ResetWhenOptions & {
|
|
63
|
+
debounce?: number;
|
|
64
|
+
}): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FieldPathNode, FormValue } from '../../types';
|
|
2
|
+
import { RevalidateWhenOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Перевалидирует поле при изменении других полей
|
|
5
|
+
*
|
|
6
|
+
* @group Behaviors
|
|
7
|
+
* @category Behavior Rules
|
|
8
|
+
*
|
|
9
|
+
* @param target - Поле для перевалидации
|
|
10
|
+
* @param triggers - Поля-триггеры (НЕ должно содержать `target`)
|
|
11
|
+
* @param options - Опции (`debounce`)
|
|
12
|
+
*
|
|
13
|
+
* @example Парная перевалидация — confirmPassword при смене password
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { revalidateWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
16
|
+
* import { equalTo } from '@reformer/core/validators';
|
|
17
|
+
* import type { FieldPath } from '@reformer/core';
|
|
18
|
+
*
|
|
19
|
+
* interface RegistrationForm { password: string; confirmPassword: string }
|
|
20
|
+
*
|
|
21
|
+
* export const validation = (path: FieldPath<RegistrationForm>) => {
|
|
22
|
+
* equalTo(path.confirmPassword, path.password, { message: 'Пароли не совпадают' });
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* export const behavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
26
|
+
* // Если пользователь сначала ввёл confirm, потом меняет password —
|
|
27
|
+
* // без revalidateWhen ошибка confirmPassword останется устаревшей.
|
|
28
|
+
* revalidateWhen(path.confirmPassword, [path.password]);
|
|
29
|
+
* };
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Несколько триггеров + `debounce` для async-валидаторов
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { revalidateWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
35
|
+
*
|
|
36
|
+
* interface MortgageForm {
|
|
37
|
+
* propertyValue: number;
|
|
38
|
+
* loanAmount: number;
|
|
39
|
+
* initialPayment: number; // правило: initialPayment >= propertyValue * 0.2 - loanAmount
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* export const mortgageBehavior: BehaviorSchemaFn<MortgageForm> = (path) => {
|
|
43
|
+
* revalidateWhen(
|
|
44
|
+
* path.initialPayment,
|
|
45
|
+
* [path.propertyValue, path.loanAmount],
|
|
46
|
+
* { debounce: 300 }, // не дёргаем сервер на каждый keystroke
|
|
47
|
+
* );
|
|
48
|
+
* };
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @see [docs/llms/27-revalidate-when.md](../../../../docs/llms/27-revalidate-when.md)
|
|
52
|
+
*/
|
|
53
|
+
export declare function revalidateWhen<TForm>(target: FieldPathNode<TForm, FormValue>, triggers: FieldPathNode<TForm, FormValue>[], options?: RevalidateWhenOptions): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
2
|
+
import { SyncFieldsOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Двусторонняя синхронизация двух полей
|
|
5
|
+
*
|
|
6
|
+
* @group Behaviors
|
|
7
|
+
* @category Behavior Rules
|
|
8
|
+
*
|
|
9
|
+
* @param field1 - Первое поле
|
|
10
|
+
* @param field2 - Второе поле
|
|
11
|
+
* @param options - Опции (`transform` асимметричен — применяется только field1 → field2; `debounce`)
|
|
12
|
+
*
|
|
13
|
+
* @example Базовый mirror двух текстовых полей
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { syncFields, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
16
|
+
*
|
|
17
|
+
* interface MirrorForm {
|
|
18
|
+
* syncField1: string;
|
|
19
|
+
* syncField2: string;
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* export const mirrorBehavior: BehaviorSchemaFn<MirrorForm> = (path) => {
|
|
23
|
+
* syncFields(path.syncField1, path.syncField2);
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example С `transform` (асимметричный) и `debounce` для защиты от частых перезаписей
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { syncFields, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
30
|
+
*
|
|
31
|
+
* interface CodeForm {
|
|
32
|
+
* internalCode: string; // канонический формат
|
|
33
|
+
* displayCode: string; // показываем пользователю
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* export const codeBehavior: BehaviorSchemaFn<CodeForm> = (path) => {
|
|
37
|
+
* // internalCode → displayCode: применяется toUpperCase
|
|
38
|
+
* // displayCode → internalCode: пишется как есть
|
|
39
|
+
* syncFields(path.internalCode, path.displayCode, {
|
|
40
|
+
* transform: (value) => (typeof value === 'string' ? value.toUpperCase() : value),
|
|
41
|
+
* debounce: 150, // сглаживает дёргание каретки
|
|
42
|
+
* });
|
|
43
|
+
* };
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @see [docs/llms/24-sync-fields.md](../../../../docs/llms/24-sync-fields.md)
|
|
47
|
+
*/
|
|
48
|
+
export declare function syncFields<TForm extends FormFields, T extends FormValue>(field1: FieldPathNode<TForm, T>, field2: FieldPathNode<TForm, T>, options?: SyncFieldsOptions<T>): void;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Опции для transformValue
|
|
4
|
+
*
|
|
5
|
+
* @group Behaviors
|
|
6
|
+
* @category Behavior Types
|
|
7
|
+
*/
|
|
8
|
+
export interface TransformValueOptions {
|
|
9
|
+
/** Трансформировать только при изменении пользователем (не программно) */
|
|
10
|
+
onUserChangeOnly?: boolean;
|
|
11
|
+
/** Триггерить событие изменения после трансформации */
|
|
12
|
+
emitEvent?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Трансформация значения поля при изменении
|
|
16
|
+
* Позволяет автоматически форматировать или преобразовывать значения
|
|
17
|
+
*
|
|
18
|
+
* @group Behaviors
|
|
19
|
+
* @category Behavior Rules
|
|
20
|
+
*
|
|
21
|
+
* @param field - Поле для трансформации
|
|
22
|
+
* @param transformer - Функция трансформации (ОБЯЗАТЕЛЬНО идемпотентная: f(f(x)) === f(x))
|
|
23
|
+
* @param options - Опции (`onUserChangeOnly`, `emitEvent`, `debounce`)
|
|
24
|
+
*
|
|
25
|
+
* @example Базовая нормализация — uppercase + trim email
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
28
|
+
*
|
|
29
|
+
* interface RegistrationForm {
|
|
30
|
+
* promoCode: string;
|
|
31
|
+
* email: string;
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* export const registrationBehavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
35
|
+
* // Идемпотентно: toUpperCase(toUpperCase(x)) === toUpperCase(x) ✓
|
|
36
|
+
* transformValue(path.promoCode, (value) => (value ?? '').toUpperCase());
|
|
37
|
+
* transformValue(path.email, (value) => (value ?? '').trim().toLowerCase());
|
|
38
|
+
* };
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example `onUserChangeOnly` + idempotent guard для не-тривиальных форматов
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
44
|
+
*
|
|
45
|
+
* interface ProfileForm {
|
|
46
|
+
* inn: string; // ИНН — только цифры
|
|
47
|
+
* prefixedCode: string; // должен иметь префикс "ID-"
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
51
|
+
* // Цифры из ИНН: трансформер идемпотентный естественно
|
|
52
|
+
* transformValue(path.inn, (v) => (v ?? '').replace(/\D/g, ''));
|
|
53
|
+
*
|
|
54
|
+
* // Префикс — ВАЖНО guard «уже преобразовано», иначе бесконечный цикл
|
|
55
|
+
* // f("ID-123") должно === "ID-123", а не "ID-ID-123"
|
|
56
|
+
* transformValue(
|
|
57
|
+
* path.prefixedCode,
|
|
58
|
+
* (v) => (v?.startsWith('ID-') ? v : `ID-${v ?? ''}`),
|
|
59
|
+
* {
|
|
60
|
+
* onUserChangeOnly: true, // не трогаем значение из patchValue/preload
|
|
61
|
+
* debounce: 200,
|
|
62
|
+
* },
|
|
63
|
+
* );
|
|
64
|
+
* };
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @see [docs/llms/26-transform-value.md](../../../../docs/llms/26-transform-value.md)
|
|
68
|
+
*/
|
|
69
|
+
export declare function transformValue<TForm extends FormFields, TValue extends FormValue = FormValue>(field: FieldPathNode<TForm, TValue>, transformer: (value: TValue) => TValue, options?: TransformValueOptions & {
|
|
70
|
+
debounce?: number;
|
|
71
|
+
}): void;
|
|
72
|
+
/**
|
|
73
|
+
* Хелпер для создания переиспользуемых трансформаций
|
|
74
|
+
*
|
|
75
|
+
* @group Behaviors
|
|
76
|
+
* @category Behavior Rules
|
|
77
|
+
*
|
|
78
|
+
* @param transformer - Идемпотентная функция преобразования значения
|
|
79
|
+
* @param defaultOptions - Опции, применяемые ко всем вызовам созданного трансформера
|
|
80
|
+
*
|
|
81
|
+
* @example Доменно-специфичные трансформеры (банковский счёт, СНИЛС)
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
84
|
+
*
|
|
85
|
+
* // Сохраняем только цифры и форматируем СНИЛС: 000-000-000 00
|
|
86
|
+
* const formatSnils = createTransformer<string>((v) => {
|
|
87
|
+
* const d = (v ?? '').replace(/\D/g, '').slice(0, 11);
|
|
88
|
+
* if (d.length < 9) return d;
|
|
89
|
+
* return `${d.slice(0, 3)}-${d.slice(3, 6)}-${d.slice(6, 9)}${d.length > 9 ? ' ' + d.slice(9) : ''}`;
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* interface ProfileForm { snils: string }
|
|
93
|
+
*
|
|
94
|
+
* export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
95
|
+
* formatSnils(path.snils, { debounce: 100 });
|
|
96
|
+
* };
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @example С `defaultOptions` — единые настройки на серию полей
|
|
100
|
+
* ```typescript
|
|
101
|
+
* import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
102
|
+
*
|
|
103
|
+
* // Все коды должны быть uppercase, но только после правки пользователем
|
|
104
|
+
* const upperOnUserEdit = createTransformer<string>(
|
|
105
|
+
* (v) => (v ?? '').toUpperCase(),
|
|
106
|
+
* { onUserChangeOnly: true, debounce: 100 },
|
|
107
|
+
* );
|
|
108
|
+
*
|
|
109
|
+
* interface PromoForm { promoCode: string; partnerCode: string }
|
|
110
|
+
*
|
|
111
|
+
* export const promoBehavior: BehaviorSchemaFn<PromoForm> = (path) => {
|
|
112
|
+
* upperOnUserEdit(path.promoCode);
|
|
113
|
+
* upperOnUserEdit(path.partnerCode);
|
|
114
|
+
* };
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function createTransformer<TValue extends FormValue = FormValue>(transformer: (value: TValue) => TValue, defaultOptions?: TransformValueOptions): <TForm extends FormFields>(field: FieldPathNode<TForm, TValue>, options?: TransformValueOptions & {
|
|
118
|
+
debounce?: number;
|
|
119
|
+
}) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Готовые трансформеры для частых случаев. Все идемпотентны и безопасны для повторного применения.
|
|
122
|
+
*
|
|
123
|
+
* @group Behaviors
|
|
124
|
+
* @category Behavior Rules
|
|
125
|
+
*
|
|
126
|
+
* @example Готовые трансформеры в схеме формы
|
|
127
|
+
* ```typescript
|
|
128
|
+
* import { transformers, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
129
|
+
*
|
|
130
|
+
* interface RegistrationForm {
|
|
131
|
+
* username: string;
|
|
132
|
+
* email: string;
|
|
133
|
+
* promoCode: string;
|
|
134
|
+
* inn: string;
|
|
135
|
+
* amount: number;
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* export const behavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
139
|
+
* transformers.trim(path.username);
|
|
140
|
+
* transformers.toLowerCase(path.email);
|
|
141
|
+
* transformers.toUpperCase(path.promoCode);
|
|
142
|
+
* transformers.digitsOnly(path.inn);
|
|
143
|
+
* transformers.roundTo2(path.amount);
|
|
144
|
+
* };
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @example Композиция готовых трансформеров через createTransformer
|
|
148
|
+
* ```typescript
|
|
149
|
+
* import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
150
|
+
*
|
|
151
|
+
* // trim + lowercase в одном трансформере (применяется как одна операция)
|
|
152
|
+
* const normalizeEmail = createTransformer<string>(
|
|
153
|
+
* (v) => (v ?? '').trim().toLowerCase(),
|
|
154
|
+
* );
|
|
155
|
+
*
|
|
156
|
+
* interface ContactForm { email: string }
|
|
157
|
+
*
|
|
158
|
+
* export const contactBehavior: BehaviorSchemaFn<ContactForm> = (path) => {
|
|
159
|
+
* normalizeEmail(path.email);
|
|
160
|
+
* };
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export declare const transformers: {
|
|
164
|
+
/** Перевести в верхний регистр */
|
|
165
|
+
toUpperCase: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
166
|
+
debounce?: number;
|
|
167
|
+
}) | undefined) => void;
|
|
168
|
+
/** Перевести в нижний регистр */
|
|
169
|
+
toLowerCase: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
170
|
+
debounce?: number;
|
|
171
|
+
}) | undefined) => void;
|
|
172
|
+
/** Удалить пробелы с краев */
|
|
173
|
+
trim: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
174
|
+
debounce?: number;
|
|
175
|
+
}) | undefined) => void;
|
|
176
|
+
/** Удалить все пробелы */
|
|
177
|
+
removeSpaces: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
178
|
+
debounce?: number;
|
|
179
|
+
}) | undefined) => void;
|
|
180
|
+
/** Оставить только цифры */
|
|
181
|
+
digitsOnly: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
182
|
+
debounce?: number;
|
|
183
|
+
}) | undefined) => void;
|
|
184
|
+
/** Округлить число */
|
|
185
|
+
round: <TForm extends FormFields>(field: FieldPathNode<TForm, number, unknown>, options?: (TransformValueOptions & {
|
|
186
|
+
debounce?: number;
|
|
187
|
+
}) | undefined) => void;
|
|
188
|
+
/** Округлить до 2 знаков после запятой */
|
|
189
|
+
roundTo2: <TForm extends FormFields>(field: FieldPathNode<TForm, number, unknown>, options?: (TransformValueOptions & {
|
|
190
|
+
debounce?: number;
|
|
191
|
+
}) | undefined) => void;
|
|
192
|
+
};
|