@reformer/core 1.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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/behaviors.d.ts +2 -0
- package/dist/behaviors.js +230 -0
- package/dist/core/behavior/behavior-applicator.d.ts +71 -0
- package/dist/core/behavior/behavior-applicator.js +92 -0
- package/dist/core/behavior/behavior-context.d.ts +29 -0
- package/dist/core/behavior/behavior-context.js +38 -0
- package/dist/core/behavior/behavior-registry.d.ts +97 -0
- package/dist/core/behavior/behavior-registry.js +198 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +41 -0
- package/dist/core/behavior/behaviors/compute-from.js +84 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +31 -0
- package/dist/core/behavior/behaviors/copy-from.js +64 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +49 -0
- package/dist/core/behavior/behaviors/enable-when.js +81 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/index.js +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +51 -0
- package/dist/core/behavior/behaviors/reset-when.js +63 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +30 -0
- package/dist/core/behavior/behaviors/revalidate-when.js +51 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +28 -0
- package/dist/core/behavior/behaviors/sync-fields.js +66 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +120 -0
- package/dist/core/behavior/behaviors/transform-value.js +110 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +35 -0
- package/dist/core/behavior/behaviors/watch-field.js +56 -0
- package/dist/core/behavior/compose-behavior.d.ts +106 -0
- package/dist/core/behavior/compose-behavior.js +166 -0
- package/dist/core/behavior/create-field-path.d.ts +20 -0
- package/dist/core/behavior/create-field-path.js +69 -0
- package/dist/core/behavior/index.d.ts +12 -0
- package/dist/core/behavior/index.js +17 -0
- package/dist/core/behavior/types.d.ts +152 -0
- package/dist/core/behavior/types.js +7 -0
- package/dist/core/context/form-context-impl.d.ts +29 -0
- package/dist/core/context/form-context-impl.js +37 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/index.js +6 -0
- package/dist/core/factories/node-factory.d.ts +209 -0
- package/dist/core/factories/node-factory.js +281 -0
- package/dist/core/nodes/array-node.d.ts +308 -0
- package/dist/core/nodes/array-node.js +534 -0
- package/dist/core/nodes/field-node.d.ts +269 -0
- package/dist/core/nodes/field-node.js +510 -0
- package/dist/core/nodes/form-node.d.ts +342 -0
- package/dist/core/nodes/form-node.js +343 -0
- package/dist/core/nodes/group-node/field-registry.d.ts +191 -0
- package/dist/core/nodes/group-node/field-registry.js +215 -0
- package/dist/core/nodes/group-node/index.d.ts +11 -0
- package/dist/core/nodes/group-node/index.js +11 -0
- package/dist/core/nodes/group-node/proxy-builder.d.ts +71 -0
- package/dist/core/nodes/group-node/proxy-builder.js +161 -0
- package/dist/core/nodes/group-node/state-manager.d.ts +184 -0
- package/dist/core/nodes/group-node/state-manager.js +265 -0
- package/dist/core/nodes/group-node.d.ts +494 -0
- package/dist/core/nodes/group-node.js +770 -0
- package/dist/core/types/deep-schema.d.ts +78 -0
- package/dist/core/types/deep-schema.js +11 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/field-path.js +4 -0
- package/dist/core/types/form-context.d.ts +83 -0
- package/dist/core/types/form-context.js +25 -0
- package/dist/core/types/group-node-proxy.d.ts +135 -0
- package/dist/core/types/group-node-proxy.js +31 -0
- package/dist/core/types/index.d.ts +163 -0
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/validation-schema.d.ts +104 -0
- package/dist/core/types/validation-schema.js +10 -0
- package/dist/core/utils/create-form.d.ts +61 -0
- package/dist/core/utils/create-form.js +24 -0
- package/dist/core/utils/debounce.d.ts +160 -0
- package/dist/core/utils/debounce.js +197 -0
- package/dist/core/utils/error-handler.d.ts +180 -0
- package/dist/core/utils/error-handler.js +226 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path-navigator.js +374 -0
- package/dist/core/utils/index.d.ts +14 -0
- package/dist/core/utils/index.js +14 -0
- package/dist/core/utils/registry-helpers.d.ts +50 -0
- package/dist/core/utils/registry-helpers.js +79 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/registry-stack.js +86 -0
- package/dist/core/utils/resources.d.ts +41 -0
- package/dist/core/utils/resources.js +69 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/subscription-manager.js +214 -0
- package/dist/core/utils/type-guards.d.ts +116 -0
- package/dist/core/utils/type-guards.js +169 -0
- package/dist/core/validation/core/apply-when.d.ts +28 -0
- package/dist/core/validation/core/apply-when.js +41 -0
- package/dist/core/validation/core/apply.d.ts +63 -0
- package/dist/core/validation/core/apply.js +38 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/index.js +8 -0
- package/dist/core/validation/core/validate-async.d.ts +42 -0
- package/dist/core/validation/core/validate-async.js +45 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate-tree.js +37 -0
- package/dist/core/validation/core/validate.d.ts +32 -0
- package/dist/core/validation/core/validate.js +38 -0
- package/dist/core/validation/field-path.d.ts +43 -0
- package/dist/core/validation/field-path.js +147 -0
- package/dist/core/validation/index.d.ts +21 -0
- package/dist/core/validation/index.js +33 -0
- package/dist/core/validation/validate-form.d.ts +85 -0
- package/dist/core/validation/validate-form.js +152 -0
- package/dist/core/validation/validation-applicator.d.ts +89 -0
- package/dist/core/validation/validation-applicator.js +217 -0
- package/dist/core/validation/validation-context.d.ts +47 -0
- package/dist/core/validation/validation-context.js +75 -0
- package/dist/core/validation/validation-registry.d.ts +156 -0
- package/dist/core/validation/validation-registry.js +298 -0
- package/dist/core/validation/validators/array-validators.d.ts +63 -0
- package/dist/core/validation/validators/array-validators.js +86 -0
- package/dist/core/validation/validators/date.d.ts +38 -0
- package/dist/core/validation/validators/date.js +117 -0
- package/dist/core/validation/validators/email.d.ts +44 -0
- package/dist/core/validation/validators/email.js +60 -0
- package/dist/core/validation/validators/index.d.ts +14 -0
- package/dist/core/validation/validators/index.js +14 -0
- package/dist/core/validation/validators/max-length.d.ts +45 -0
- package/dist/core/validation/validators/max-length.js +60 -0
- package/dist/core/validation/validators/max.d.ts +45 -0
- package/dist/core/validation/validators/max.js +60 -0
- package/dist/core/validation/validators/min-length.d.ts +45 -0
- package/dist/core/validation/validators/min-length.js +60 -0
- package/dist/core/validation/validators/min.d.ts +45 -0
- package/dist/core/validation/validators/min.js +60 -0
- package/dist/core/validation/validators/number.d.ts +38 -0
- package/dist/core/validation/validators/number.js +90 -0
- package/dist/core/validation/validators/pattern.d.ts +47 -0
- package/dist/core/validation/validators/pattern.js +62 -0
- package/dist/core/validation/validators/phone.d.ts +34 -0
- package/dist/core/validation/validators/phone.js +58 -0
- package/dist/core/validation/validators/required.d.ts +48 -0
- package/dist/core/validation/validators/required.js +69 -0
- package/dist/core/validation/validators/url.d.ts +29 -0
- package/dist/core/validation/validators/url.js +55 -0
- package/dist/create-field-path-CdPF3lIK.js +704 -0
- package/dist/hooks/useFormControl.d.ts +48 -0
- package/dist/hooks/useFormControl.js +298 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/node-factory-D7DOnSSN.js +3200 -0
- package/dist/validators.d.ts +2 -0
- package/dist/validators.js +298 -0
- package/llms.txt +847 -0
- package/package.json +86 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Условный сброс полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/resetWhen
|
|
7
|
+
*/
|
|
8
|
+
import { effect } from '@preact/signals-core';
|
|
9
|
+
import { getCurrentBehaviorRegistry } from '../../utils/registry-helpers';
|
|
10
|
+
/**
|
|
11
|
+
* Условный сброс поля при выполнении условия
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field - Поле для сброса
|
|
17
|
+
* @param condition - Функция условия (true = reset)
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Сбросить поле при изменении типа кредита
|
|
24
|
+
* resetWhen(path.propertyValue, (form) => form.loanType !== 'mortgage');
|
|
25
|
+
*
|
|
26
|
+
* // Сбросить с кастомным значением
|
|
27
|
+
* resetWhen(path.initialPayment, (form) => !form.propertyValue, {
|
|
28
|
+
* resetValue: 0
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Сбросить только если поле было изменено пользователем
|
|
32
|
+
* resetWhen(path.carPrice, (form) => form.loanType !== 'car', {
|
|
33
|
+
* onlyIfDirty: true
|
|
34
|
+
* });
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function resetWhen(field, condition, options) {
|
|
39
|
+
const { debounce, resetValue = null, onlyIfDirty = false } = options || {};
|
|
40
|
+
const handler = (form, _context, withDebounce) => {
|
|
41
|
+
const targetNode = form.getFieldByPath(field.__path);
|
|
42
|
+
if (!targetNode)
|
|
43
|
+
return null;
|
|
44
|
+
return effect(() => {
|
|
45
|
+
const formValue = form.value.value;
|
|
46
|
+
withDebounce(() => {
|
|
47
|
+
const shouldReset = condition(formValue);
|
|
48
|
+
if (shouldReset) {
|
|
49
|
+
// Проверяем onlyIfDirty опцию
|
|
50
|
+
if (onlyIfDirty && !targetNode.dirty.value) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Сбрасываем значение
|
|
54
|
+
targetNode.setValue(resetValue);
|
|
55
|
+
// Сбрасываем флаги dirty и touched
|
|
56
|
+
targetNode.markAsPristine();
|
|
57
|
+
targetNode.markAsUntouched();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
63
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевалидация полей при изменениях
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/revalidateWhen
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode, FormValue } from '../../types';
|
|
9
|
+
import type { RevalidateWhenOptions } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Перевалидирует поле при изменении других полей
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param target - Поле для перевалидации
|
|
17
|
+
* @param triggers - Поля-триггеры
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Перевалидировать initialPayment при изменении propertyValue
|
|
24
|
+
* revalidateWhen(path.initialPayment, [path.propertyValue], {
|
|
25
|
+
* debounce: 300
|
|
26
|
+
* });
|
|
27
|
+
* };
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function revalidateWhen<TForm>(target: FieldPathNode<TForm, FormValue>, triggers: FieldPathNode<TForm, FormValue>[], options?: RevalidateWhenOptions): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевалидация полей при изменениях
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/revalidateWhen
|
|
7
|
+
*/
|
|
8
|
+
import { effect } from '@preact/signals-core';
|
|
9
|
+
import { getCurrentBehaviorRegistry } from '../../utils/registry-helpers';
|
|
10
|
+
/**
|
|
11
|
+
* Перевалидирует поле при изменении других полей
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param target - Поле для перевалидации
|
|
17
|
+
* @param triggers - Поля-триггеры
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Перевалидировать initialPayment при изменении propertyValue
|
|
24
|
+
* revalidateWhen(path.initialPayment, [path.propertyValue], {
|
|
25
|
+
* debounce: 300
|
|
26
|
+
* });
|
|
27
|
+
* };
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function revalidateWhen(target, triggers, options) {
|
|
31
|
+
const { debounce } = options || {};
|
|
32
|
+
const handler = (form, _context, withDebounce) => {
|
|
33
|
+
const targetNode = form.getFieldByPath(target.__path);
|
|
34
|
+
if (!targetNode)
|
|
35
|
+
return null;
|
|
36
|
+
const sourceNodes = triggers
|
|
37
|
+
.map((field) => form.getFieldByPath(field.__path))
|
|
38
|
+
.filter((node) => node !== undefined);
|
|
39
|
+
if (sourceNodes.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
return effect(() => {
|
|
42
|
+
// Отслеживаем изменения source полей
|
|
43
|
+
sourceNodes.forEach((node) => node.value.value);
|
|
44
|
+
withDebounce(() => {
|
|
45
|
+
// Перезапускаем валидацию target поля
|
|
46
|
+
targetNode.validate();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
51
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Двусторонняя синхронизация полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/syncFields
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
9
|
+
import type { SyncFieldsOptions } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Двусторонняя синхронизация двух полей
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field1 - Первое поле
|
|
17
|
+
* @param field2 - Второе поле
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Синхронизировать два поля
|
|
24
|
+
* syncFields(path.email, path.emailCopy);
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Двусторонняя синхронизация полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/syncFields
|
|
7
|
+
*/
|
|
8
|
+
import { effect } from '@preact/signals-core';
|
|
9
|
+
import { getCurrentBehaviorRegistry } from '../../utils/registry-helpers';
|
|
10
|
+
/**
|
|
11
|
+
* Двусторонняя синхронизация двух полей
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field1 - Первое поле
|
|
17
|
+
* @param field2 - Второе поле
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Синхронизировать два поля
|
|
24
|
+
* syncFields(path.email, path.emailCopy);
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function syncFields(field1, field2, options) {
|
|
29
|
+
const { debounce, transform } = options || {};
|
|
30
|
+
const handler = (form, _context, withDebounce) => {
|
|
31
|
+
const sourceNode = form.getFieldByPath(field1.__path);
|
|
32
|
+
const targetNode = form.getFieldByPath(field2.__path);
|
|
33
|
+
if (!sourceNode || !targetNode)
|
|
34
|
+
return null;
|
|
35
|
+
// Флаг для предотвращения циклических обновлений
|
|
36
|
+
let isUpdating = false;
|
|
37
|
+
const dispose1 = effect(() => {
|
|
38
|
+
const sourceValue = sourceNode.value.value;
|
|
39
|
+
if (isUpdating)
|
|
40
|
+
return;
|
|
41
|
+
withDebounce(() => {
|
|
42
|
+
isUpdating = true;
|
|
43
|
+
const finalValue = transform ? transform(sourceValue) : sourceValue;
|
|
44
|
+
targetNode.setValue(finalValue, { emitEvent: false });
|
|
45
|
+
isUpdating = false;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
const dispose2 = effect(() => {
|
|
49
|
+
const targetValue = targetNode.value.value;
|
|
50
|
+
if (isUpdating)
|
|
51
|
+
return;
|
|
52
|
+
withDebounce(() => {
|
|
53
|
+
isUpdating = true;
|
|
54
|
+
// Обратная синхронизация (без трансформации)
|
|
55
|
+
sourceNode.setValue(targetValue, { emitEvent: false });
|
|
56
|
+
isUpdating = false;
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
// Возвращаем комбинированный cleanup
|
|
60
|
+
return () => {
|
|
61
|
+
dispose1();
|
|
62
|
+
dispose2();
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
66
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Трансформация значений полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/transformValue
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
9
|
+
/**
|
|
10
|
+
* Опции для transformValue
|
|
11
|
+
*
|
|
12
|
+
* @group Behaviors
|
|
13
|
+
* @category Behavior Types
|
|
14
|
+
*/
|
|
15
|
+
export interface TransformValueOptions {
|
|
16
|
+
/** Трансформировать только при изменении пользователем (не программно) */
|
|
17
|
+
onUserChangeOnly?: boolean;
|
|
18
|
+
/** Триггерить событие изменения после трансформации */
|
|
19
|
+
emitEvent?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Трансформация значения поля при изменении
|
|
23
|
+
* Позволяет автоматически форматировать или преобразовывать значения
|
|
24
|
+
*
|
|
25
|
+
* @group Behaviors
|
|
26
|
+
* @category Behavior Rules
|
|
27
|
+
*
|
|
28
|
+
* @param field - Поле для трансформации
|
|
29
|
+
* @param transformer - Функция трансформации
|
|
30
|
+
* @param options - Опции
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
35
|
+
* // Автоматически переводить текст в верхний регистр
|
|
36
|
+
* transformValue(path.code, (value) => value?.toUpperCase());
|
|
37
|
+
*
|
|
38
|
+
* // Форматировать номер телефона
|
|
39
|
+
* transformValue(path.phone, (value) => {
|
|
40
|
+
* if (!value) return value;
|
|
41
|
+
* const digits = value.replace(/\D/g, '');
|
|
42
|
+
* if (digits.length === 11) {
|
|
43
|
+
* return `+7 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7, 9)}-${digits.slice(9)}`;
|
|
44
|
+
* }
|
|
45
|
+
* return value;
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Удалять пробелы из email
|
|
49
|
+
* transformValue(path.email, (value) => value?.trim().toLowerCase());
|
|
50
|
+
*
|
|
51
|
+
* // Округлять числа
|
|
52
|
+
* transformValue(path.amount, (value) => {
|
|
53
|
+
* return typeof value === 'number' ? Math.round(value) : value;
|
|
54
|
+
* });
|
|
55
|
+
* };
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function transformValue<TForm extends FormFields, TValue extends FormValue = FormValue>(field: FieldPathNode<TForm, TValue>, transformer: (value: TValue) => TValue, options?: TransformValueOptions & {
|
|
59
|
+
debounce?: number;
|
|
60
|
+
}): void;
|
|
61
|
+
/**
|
|
62
|
+
* Хелпер для создания переиспользуемых трансформаций
|
|
63
|
+
*
|
|
64
|
+
* @group Behaviors
|
|
65
|
+
* @category Behavior Rules
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Создаем переиспользуемые трансформеры
|
|
70
|
+
* const toUpperCase = createTransformer<string>((value) => value?.toUpperCase());
|
|
71
|
+
* const toLowerCase = createTransformer<string>((value) => value?.toLowerCase());
|
|
72
|
+
* const trim = createTransformer<string>((value) => value?.trim());
|
|
73
|
+
*
|
|
74
|
+
* // Используем в форме
|
|
75
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
76
|
+
* toUpperCase(path.code);
|
|
77
|
+
* toLowerCase(path.email);
|
|
78
|
+
* trim(path.username);
|
|
79
|
+
* };
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function createTransformer<TValue extends FormValue = FormValue>(transformer: (value: TValue) => TValue, defaultOptions?: TransformValueOptions): <TForm extends FormFields>(field: FieldPathNode<TForm, TValue>, options?: TransformValueOptions & {
|
|
83
|
+
debounce?: number;
|
|
84
|
+
}) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Готовые трансформеры для частых случаев
|
|
87
|
+
*
|
|
88
|
+
* @group Behaviors
|
|
89
|
+
* @category Behavior Rules
|
|
90
|
+
*/
|
|
91
|
+
export declare const transformers: {
|
|
92
|
+
/** Перевести в верхний регистр */
|
|
93
|
+
toUpperCase: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
94
|
+
debounce?: number;
|
|
95
|
+
}) | undefined) => void;
|
|
96
|
+
/** Перевести в нижний регистр */
|
|
97
|
+
toLowerCase: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
98
|
+
debounce?: number;
|
|
99
|
+
}) | undefined) => void;
|
|
100
|
+
/** Удалить пробелы с краев */
|
|
101
|
+
trim: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
102
|
+
debounce?: number;
|
|
103
|
+
}) | undefined) => void;
|
|
104
|
+
/** Удалить все пробелы */
|
|
105
|
+
removeSpaces: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
106
|
+
debounce?: number;
|
|
107
|
+
}) | undefined) => void;
|
|
108
|
+
/** Оставить только цифры */
|
|
109
|
+
digitsOnly: <TForm extends FormFields>(field: FieldPathNode<TForm, string, unknown>, options?: (TransformValueOptions & {
|
|
110
|
+
debounce?: number;
|
|
111
|
+
}) | undefined) => void;
|
|
112
|
+
/** Округлить число */
|
|
113
|
+
round: <TForm extends FormFields>(field: FieldPathNode<TForm, number, unknown>, options?: (TransformValueOptions & {
|
|
114
|
+
debounce?: number;
|
|
115
|
+
}) | undefined) => void;
|
|
116
|
+
/** Округлить до 2 знаков после запятой */
|
|
117
|
+
roundTo2: <TForm extends FormFields>(field: FieldPathNode<TForm, number, unknown>, options?: (TransformValueOptions & {
|
|
118
|
+
debounce?: number;
|
|
119
|
+
}) | undefined) => void;
|
|
120
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Трансформация значений полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/transformValue
|
|
7
|
+
*/
|
|
8
|
+
import { watchField } from './watch-field';
|
|
9
|
+
/**
|
|
10
|
+
* Трансформация значения поля при изменении
|
|
11
|
+
* Позволяет автоматически форматировать или преобразовывать значения
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field - Поле для трансформации
|
|
17
|
+
* @param transformer - Функция трансформации
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Автоматически переводить текст в верхний регистр
|
|
24
|
+
* transformValue(path.code, (value) => value?.toUpperCase());
|
|
25
|
+
*
|
|
26
|
+
* // Форматировать номер телефона
|
|
27
|
+
* transformValue(path.phone, (value) => {
|
|
28
|
+
* if (!value) return value;
|
|
29
|
+
* const digits = value.replace(/\D/g, '');
|
|
30
|
+
* if (digits.length === 11) {
|
|
31
|
+
* return `+7 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7, 9)}-${digits.slice(9)}`;
|
|
32
|
+
* }
|
|
33
|
+
* return value;
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Удалять пробелы из email
|
|
37
|
+
* transformValue(path.email, (value) => value?.trim().toLowerCase());
|
|
38
|
+
*
|
|
39
|
+
* // Округлять числа
|
|
40
|
+
* transformValue(path.amount, (value) => {
|
|
41
|
+
* return typeof value === 'number' ? Math.round(value) : value;
|
|
42
|
+
* });
|
|
43
|
+
* };
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function transformValue(field, transformer, options) {
|
|
47
|
+
const { onUserChangeOnly = false, emitEvent = true, debounce } = options || {};
|
|
48
|
+
watchField(field, (currentValue, ctx) => {
|
|
49
|
+
const targetNode = ctx.form.getFieldByPath(field.__path);
|
|
50
|
+
if (!targetNode)
|
|
51
|
+
return;
|
|
52
|
+
// Если нужно трансформировать только при изменении пользователем
|
|
53
|
+
if (onUserChangeOnly && !targetNode.touched.value) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const transformedValue = transformer(currentValue);
|
|
57
|
+
// Применяем трансформацию только если значение изменилось
|
|
58
|
+
if (transformedValue !== currentValue) {
|
|
59
|
+
targetNode.setValue(transformedValue, { emitEvent });
|
|
60
|
+
}
|
|
61
|
+
}, { debounce });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Хелпер для создания переиспользуемых трансформаций
|
|
65
|
+
*
|
|
66
|
+
* @group Behaviors
|
|
67
|
+
* @category Behavior Rules
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Создаем переиспользуемые трансформеры
|
|
72
|
+
* const toUpperCase = createTransformer<string>((value) => value?.toUpperCase());
|
|
73
|
+
* const toLowerCase = createTransformer<string>((value) => value?.toLowerCase());
|
|
74
|
+
* const trim = createTransformer<string>((value) => value?.trim());
|
|
75
|
+
*
|
|
76
|
+
* // Используем в форме
|
|
77
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
78
|
+
* toUpperCase(path.code);
|
|
79
|
+
* toLowerCase(path.email);
|
|
80
|
+
* trim(path.username);
|
|
81
|
+
* };
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function createTransformer(transformer, defaultOptions) {
|
|
85
|
+
return (field, options) => {
|
|
86
|
+
transformValue(field, transformer, { ...defaultOptions, ...options });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Готовые трансформеры для частых случаев
|
|
91
|
+
*
|
|
92
|
+
* @group Behaviors
|
|
93
|
+
* @category Behavior Rules
|
|
94
|
+
*/
|
|
95
|
+
export const transformers = {
|
|
96
|
+
/** Перевести в верхний регистр */
|
|
97
|
+
toUpperCase: createTransformer((value) => value?.toUpperCase()),
|
|
98
|
+
/** Перевести в нижний регистр */
|
|
99
|
+
toLowerCase: createTransformer((value) => value?.toLowerCase()),
|
|
100
|
+
/** Удалить пробелы с краев */
|
|
101
|
+
trim: createTransformer((value) => value?.trim()),
|
|
102
|
+
/** Удалить все пробелы */
|
|
103
|
+
removeSpaces: createTransformer((value) => value?.replace(/\s/g, '')),
|
|
104
|
+
/** Оставить только цифры */
|
|
105
|
+
digitsOnly: createTransformer((value) => value?.replace(/\D/g, '')),
|
|
106
|
+
/** Округлить число */
|
|
107
|
+
round: createTransformer((value) => typeof value === 'number' ? Math.round(value) : value),
|
|
108
|
+
/** Округлить до 2 знаков после запятой */
|
|
109
|
+
roundTo2: createTransformer((value) => typeof value === 'number' ? Math.round(value * 100) / 100 : value),
|
|
110
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Отслеживание изменений поля
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/watchField
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode } from '../../types';
|
|
9
|
+
import type { BehaviorContext, WatchFieldOptions } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Выполняет callback при изменении поля
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field - Поле для отслеживания
|
|
17
|
+
* @param callback - Функция обратного вызова
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Динамическая загрузка городов при изменении страны
|
|
24
|
+
* watchField(path.registrationAddress.country, async (country, ctx) => {
|
|
25
|
+
* if (country) {
|
|
26
|
+
* const cities = await fetchCities(country);
|
|
27
|
+
* ctx.updateComponentProps(path.registrationAddress.city, {
|
|
28
|
+
* options: cities
|
|
29
|
+
* });
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function watchField<TForm, TField>(field: FieldPathNode<TForm, TField>, callback: (value: TField, ctx: BehaviorContext<TForm>) => void | Promise<void>, options?: WatchFieldOptions): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Отслеживание изменений поля
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/watchField
|
|
7
|
+
*/
|
|
8
|
+
import { effect } from '@preact/signals-core';
|
|
9
|
+
import { getCurrentBehaviorRegistry } from '../../utils/registry-helpers';
|
|
10
|
+
/**
|
|
11
|
+
* Выполняет callback при изменении поля
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param field - Поле для отслеживания
|
|
17
|
+
* @param callback - Функция обратного вызова
|
|
18
|
+
* @param options - Опции
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Динамическая загрузка городов при изменении страны
|
|
24
|
+
* watchField(path.registrationAddress.country, async (country, ctx) => {
|
|
25
|
+
* if (country) {
|
|
26
|
+
* const cities = await fetchCities(country);
|
|
27
|
+
* ctx.updateComponentProps(path.registrationAddress.city, {
|
|
28
|
+
* options: cities
|
|
29
|
+
* });
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function watchField(field, callback, options) {
|
|
36
|
+
const { debounce, immediate = false } = options || {};
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const handler = (form, context, withDebounce) => {
|
|
39
|
+
const node = form.getFieldByPath(field.__path);
|
|
40
|
+
if (!node)
|
|
41
|
+
return null;
|
|
42
|
+
// Вызвать сразу если immediate: true
|
|
43
|
+
if (immediate) {
|
|
44
|
+
const value = node.value.value;
|
|
45
|
+
callback(value, context);
|
|
46
|
+
}
|
|
47
|
+
return effect(() => {
|
|
48
|
+
const value = node.value.value;
|
|
49
|
+
withDebounce(() => {
|
|
50
|
+
callback(value, context);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
56
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Композиция behavior схем
|
|
3
|
+
*
|
|
4
|
+
* Предоставляет функции для переиспользования behavior схем:
|
|
5
|
+
* - toBehaviorFieldPath: преобразование FieldPath во вложенный путь
|
|
6
|
+
* - apply: применение схемы к полям
|
|
7
|
+
* - applyWhen: условное применение схемы
|
|
8
|
+
*
|
|
9
|
+
* Аналог toFieldPath и applyWhen из validation API.
|
|
10
|
+
*/
|
|
11
|
+
import type { FieldPath, FieldPathNode, FormFields, FormValue } from '../types';
|
|
12
|
+
import type { BehaviorSchemaFn } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Преобразовать FieldPath во вложенный путь для композиции behavior схем
|
|
15
|
+
*
|
|
16
|
+
* Аналог toFieldPath из validation API.
|
|
17
|
+
*
|
|
18
|
+
* @param fieldPath - Поле для преобразования
|
|
19
|
+
* @returns Вложенный FieldPath
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // address-behavior.ts
|
|
24
|
+
* export const addressBehavior = (path: FieldPath<Address>) => {
|
|
25
|
+
* watchField(path.country, async (country, ctx) => {
|
|
26
|
+
* const regions = await fetchRegions(country);
|
|
27
|
+
* ctx.updateComponentProps(path.region, { options: regions });
|
|
28
|
+
* });
|
|
29
|
+
* };
|
|
30
|
+
*
|
|
31
|
+
* // user-behavior.ts
|
|
32
|
+
* export const userBehavior = (path: FieldPath<User>) => {
|
|
33
|
+
* // Композиция: применяем addressBehavior к вложенному полю
|
|
34
|
+
* addressBehavior(toBehaviorFieldPath(path.address));
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function toBehaviorFieldPath<TForm, TField>(fieldPath: FieldPathNode<TForm, TField> | undefined): FieldPath<TField>;
|
|
39
|
+
/**
|
|
40
|
+
* Применить behavior схему к вложенному полю или полям
|
|
41
|
+
*
|
|
42
|
+
* Поддерживает:
|
|
43
|
+
* - Одно поле или массив полей
|
|
44
|
+
* - Одну схему или массив схем
|
|
45
|
+
* - Все комбинации (поле + схема, поле + схемы, поля + схема, поля + схемы)
|
|
46
|
+
*
|
|
47
|
+
* @param fields - Одно поле или массив полей
|
|
48
|
+
* @param behaviors - Одна схема или массив схем
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Одна схема к одному полю
|
|
53
|
+
* apply(path.registrationAddress, addressBehavior);
|
|
54
|
+
*
|
|
55
|
+
* // Одна схема к нескольким полям
|
|
56
|
+
* apply([path.registrationAddress, path.residenceAddress], addressBehavior);
|
|
57
|
+
*
|
|
58
|
+
* // Несколько схем к одному полю
|
|
59
|
+
* apply(path.properties, [propertyBehavior, arrayBehavior]);
|
|
60
|
+
*
|
|
61
|
+
* // Несколько схем к нескольким полям
|
|
62
|
+
* apply(
|
|
63
|
+
* [path.registrationAddress, path.residenceAddress],
|
|
64
|
+
* [addressBehavior, validationBehavior]
|
|
65
|
+
* );
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function apply<TForm, TField>(fields: FieldPathNode<TForm, TField> | Array<FieldPathNode<TForm, TField> | undefined> | undefined, behaviors: BehaviorSchemaFn<TField> | Array<BehaviorSchemaFn<TField>>): void;
|
|
69
|
+
/**
|
|
70
|
+
* Условное применение behavior схем (аналог applyWhen из validation API)
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ ВАЖНО: Эта функция НЕ создаёт новые behaviors при каждом изменении условия!
|
|
73
|
+
* Вместо этого behaviors регистрируются ОДИН РАЗ при первом вызове и затем
|
|
74
|
+
* просто не выполняются, если условие не выполнено.
|
|
75
|
+
*
|
|
76
|
+
* Это отличается от старой реализации, которая создавала утечку памяти,
|
|
77
|
+
* регистрируя behaviors при каждом изменении conditionField.
|
|
78
|
+
*
|
|
79
|
+
* @param conditionField - Поле для проверки условия
|
|
80
|
+
* @param condition - Функция проверки условия
|
|
81
|
+
* @param callback - Callback для применения behavior схем
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Применить addressBehavior только когда sameAsRegistration === false
|
|
86
|
+
* applyWhen(
|
|
87
|
+
* path.sameAsRegistration,
|
|
88
|
+
* (value) => value === false,
|
|
89
|
+
* (path) => {
|
|
90
|
+
* apply(path.residenceAddress, addressBehavior);
|
|
91
|
+
* }
|
|
92
|
+
* );
|
|
93
|
+
*
|
|
94
|
+
* // Или с прямым использованием path
|
|
95
|
+
* applyWhen(
|
|
96
|
+
* path.hasProperty,
|
|
97
|
+
* (value) => value === true,
|
|
98
|
+
* (path) => {
|
|
99
|
+
* apply(path.properties, propertyBehavior);
|
|
100
|
+
* // Можно применить несколько схем
|
|
101
|
+
* apply([path.properties, path.items], arrayBehavior);
|
|
102
|
+
* }
|
|
103
|
+
* );
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function applyWhen<TForm extends FormFields, TValue extends FormValue>(conditionField: FieldPathNode<TForm, TValue> | undefined, condition: (value: TValue) => boolean, callback: (path: FieldPath<TForm>) => void): void;
|