@reformer/core 1.1.0-beta.7 → 1.1.0-beta.8
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-BRaiR-UY.js +528 -0
- package/dist/behaviors.d.ts +6 -2
- package/dist/behaviors.js +18 -227
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3380 -10
- package/dist/validators-DjXtDVoE.js +455 -0
- package/dist/validators.d.ts +6 -2
- package/dist/validators.js +29 -281
- package/package.json +1 -1
- package/dist/core/behavior/behavior-applicator.js +0 -92
- package/dist/core/behavior/behavior-context.js +0 -43
- package/dist/core/behavior/behavior-registry.js +0 -198
- package/dist/core/behavior/behaviors/compute-from.js +0 -84
- package/dist/core/behavior/behaviors/copy-from.js +0 -64
- package/dist/core/behavior/behaviors/enable-when.js +0 -81
- package/dist/core/behavior/behaviors/index.js +0 -11
- package/dist/core/behavior/behaviors/reset-when.js +0 -63
- package/dist/core/behavior/behaviors/revalidate-when.js +0 -51
- package/dist/core/behavior/behaviors/sync-fields.js +0 -66
- package/dist/core/behavior/behaviors/transform-value.js +0 -110
- package/dist/core/behavior/behaviors/watch-field.js +0 -56
- package/dist/core/behavior/compose-behavior.js +0 -166
- package/dist/core/behavior/create-field-path.js +0 -69
- package/dist/core/behavior/index.js +0 -17
- package/dist/core/behavior/types.js +0 -7
- package/dist/core/context/form-context-impl.js +0 -37
- package/dist/core/factories/index.js +0 -6
- package/dist/core/factories/node-factory.js +0 -281
- package/dist/core/nodes/array-node.js +0 -534
- package/dist/core/nodes/field-node.js +0 -510
- package/dist/core/nodes/form-node.js +0 -343
- package/dist/core/nodes/group-node/field-registry.js +0 -215
- package/dist/core/nodes/group-node/index.js +0 -11
- package/dist/core/nodes/group-node/proxy-builder.js +0 -161
- package/dist/core/nodes/group-node/state-manager.js +0 -265
- package/dist/core/nodes/group-node.js +0 -770
- package/dist/core/types/deep-schema.js +0 -11
- package/dist/core/types/field-path.js +0 -4
- package/dist/core/types/form-context.js +0 -25
- package/dist/core/types/group-node-proxy.js +0 -31
- package/dist/core/types/index.js +0 -4
- package/dist/core/types/validation-schema.js +0 -10
- package/dist/core/utils/create-form.js +0 -24
- package/dist/core/utils/debounce.js +0 -197
- package/dist/core/utils/error-handler.js +0 -226
- package/dist/core/utils/field-path-navigator.js +0 -374
- package/dist/core/utils/index.js +0 -14
- package/dist/core/utils/registry-helpers.js +0 -79
- package/dist/core/utils/registry-stack.js +0 -86
- package/dist/core/utils/resources.js +0 -69
- package/dist/core/utils/subscription-manager.js +0 -214
- package/dist/core/utils/type-guards.js +0 -169
- package/dist/core/validation/core/apply-when.js +0 -41
- package/dist/core/validation/core/apply.js +0 -38
- package/dist/core/validation/core/index.js +0 -8
- package/dist/core/validation/core/validate-async.js +0 -45
- package/dist/core/validation/core/validate-tree.js +0 -43
- package/dist/core/validation/core/validate.js +0 -38
- package/dist/core/validation/field-path.js +0 -147
- package/dist/core/validation/index.js +0 -33
- package/dist/core/validation/validate-form.js +0 -152
- package/dist/core/validation/validation-applicator.js +0 -217
- package/dist/core/validation/validation-context.js +0 -75
- package/dist/core/validation/validation-registry.js +0 -298
- package/dist/core/validation/validators/array-validators.js +0 -86
- package/dist/core/validation/validators/date.js +0 -117
- package/dist/core/validation/validators/email.js +0 -60
- package/dist/core/validation/validators/index.js +0 -14
- package/dist/core/validation/validators/max-length.js +0 -60
- package/dist/core/validation/validators/max.js +0 -60
- package/dist/core/validation/validators/min-length.js +0 -60
- package/dist/core/validation/validators/min.js +0 -60
- package/dist/core/validation/validators/number.js +0 -90
- package/dist/core/validation/validators/pattern.js +0 -62
- package/dist/core/validation/validators/phone.js +0 -58
- package/dist/core/validation/validators/required.js +0 -69
- package/dist/core/validation/validators/url.js +0 -55
- package/dist/create-field-path-nXfTtl55.js +0 -283
- package/dist/hooks/useFormControl.js +0 -298
- package/dist/validation-context-cWXmh_Ho.js +0 -156
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Вычисляемые поля
|
|
3
|
-
*
|
|
4
|
-
* @group Behaviors
|
|
5
|
-
* @category Behavior Rules
|
|
6
|
-
* @module behaviors/computeFrom
|
|
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 sources - Массив полей-зависимостей
|
|
17
|
-
* @param target - Поле для записи результата
|
|
18
|
-
* @param computeFn - Функция вычисления (принимает объект с именами полей)
|
|
19
|
-
* @param options - Опции
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
24
|
-
* // Автоматический расчет минимального взноса
|
|
25
|
-
* computeFrom(
|
|
26
|
-
* [path.propertyValue],
|
|
27
|
-
* path.initialPayment,
|
|
28
|
-
* (values) => values.propertyValue ? values.propertyValue * 0.2 : null,
|
|
29
|
-
* { debounce: 300 }
|
|
30
|
-
* );
|
|
31
|
-
*
|
|
32
|
-
* // Общая стоимость = цена * количество
|
|
33
|
-
* computeFrom(
|
|
34
|
-
* [path.price, path.quantity],
|
|
35
|
-
* path.total,
|
|
36
|
-
* (values) => values.price * values.quantity
|
|
37
|
-
* );
|
|
38
|
-
* };
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export function computeFrom(
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
-
sources, target, computeFn, options) {
|
|
44
|
-
const { debounce, condition } = options || {};
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
-
const handler = (form, _context, withDebounce) => {
|
|
47
|
-
const targetNode = form.getFieldByPath(target.__path);
|
|
48
|
-
if (!targetNode)
|
|
49
|
-
return null;
|
|
50
|
-
// Разрешаем source узлы
|
|
51
|
-
const sourceNodes = sources
|
|
52
|
-
.map((field) => form.getFieldByPath(field.__path))
|
|
53
|
-
.filter((node) => node !== undefined);
|
|
54
|
-
if (sourceNodes.length === 0)
|
|
55
|
-
return null;
|
|
56
|
-
return effect(() => {
|
|
57
|
-
// Читаем значения всех source полей
|
|
58
|
-
const sourceValues = sourceNodes.map((node) => node.value.value);
|
|
59
|
-
withDebounce(() => {
|
|
60
|
-
// Проверка условия
|
|
61
|
-
if (condition) {
|
|
62
|
-
const formValue = form.getValue();
|
|
63
|
-
if (!condition(formValue))
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
// Создаем объект с именами полей для computeFn
|
|
67
|
-
// computeFn ожидает объект вида { fieldName: value, ... }
|
|
68
|
-
const sourceValuesObject = {};
|
|
69
|
-
sources.forEach((source, index) => {
|
|
70
|
-
// Извлекаем имя поля из пути (последний сегмент)
|
|
71
|
-
const fieldName = source.__path.split('.').pop() || source.__path;
|
|
72
|
-
sourceValuesObject[fieldName] = sourceValues[index];
|
|
73
|
-
});
|
|
74
|
-
// Вычисляем новое значение
|
|
75
|
-
const computedValue = computeFn(sourceValuesObject);
|
|
76
|
-
// Устанавливаем значение без триггера событий
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
targetNode.setValue(computedValue, { emitEvent: false });
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
-
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
84
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Копирование значений между полями
|
|
3
|
-
*
|
|
4
|
-
* @group Behaviors
|
|
5
|
-
* @category Behavior Rules
|
|
6
|
-
* @module behaviors/copyFrom
|
|
7
|
-
*/
|
|
8
|
-
import { watchField } from './watch-field';
|
|
9
|
-
/**
|
|
10
|
-
* Копирует значения из одного поля/группы в другое при выполнении условия
|
|
11
|
-
*
|
|
12
|
-
* @group Behaviors
|
|
13
|
-
* @category Behavior Rules
|
|
14
|
-
*
|
|
15
|
-
* @param source - Откуда копировать
|
|
16
|
-
* @param target - Куда копировать
|
|
17
|
-
* @param options - Опции копирования
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
22
|
-
* // Копировать адрес регистрации в адрес проживания
|
|
23
|
-
* copyFrom(path.registrationAddress, path.residenceAddress, {
|
|
24
|
-
* when: (form) => form.sameAsRegistration === true,
|
|
25
|
-
* fields: 'all'
|
|
26
|
-
* });
|
|
27
|
-
* };
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export function copyFrom(source, target, options) {
|
|
31
|
-
const { when, fields = 'all', transform, debounce } = options || {};
|
|
32
|
-
watchField(source, (sourceValue, ctx) => {
|
|
33
|
-
// Проверка условия
|
|
34
|
-
if (when) {
|
|
35
|
-
const formValue = ctx.form.getValue();
|
|
36
|
-
if (!when(formValue))
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Трансформация значения
|
|
40
|
-
const value = transform ? transform(sourceValue) : sourceValue;
|
|
41
|
-
// Получаем target node
|
|
42
|
-
const targetNode = ctx.form.getFieldByPath(target.__path);
|
|
43
|
-
if (!targetNode)
|
|
44
|
-
return;
|
|
45
|
-
// Копирование
|
|
46
|
-
if (fields === 'all' || !fields) {
|
|
47
|
-
targetNode.setValue(value, { emitEvent: false });
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// Частичное копирование для групп
|
|
51
|
-
const patch = {};
|
|
52
|
-
fields.forEach((key) => {
|
|
53
|
-
if (sourceValue && typeof sourceValue === 'object') {
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
-
patch[key] = sourceValue[key];
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
if ('patchValue' in targetNode) {
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
-
targetNode.patchValue(patch);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}, { debounce });
|
|
64
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Условное включение/отключение полей
|
|
3
|
-
*
|
|
4
|
-
* @group Behaviors
|
|
5
|
-
* @category Behavior Rules
|
|
6
|
-
* @module behaviors/enableWhen
|
|
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 = enable, false = disable)
|
|
18
|
-
* @param options - Опции
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
-
* // Включить поле только для ипотеки
|
|
24
|
-
* enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage', {
|
|
25
|
-
* resetOnDisable: true
|
|
26
|
-
* });
|
|
27
|
-
* };
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export function enableWhen(
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
-
field, condition, options) {
|
|
33
|
-
const { debounce, resetOnDisable = false } = options || {};
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
const handler = (form, _context, withDebounce) => {
|
|
36
|
-
const targetNode = form.getFieldByPath(field.__path);
|
|
37
|
-
if (!targetNode)
|
|
38
|
-
return null;
|
|
39
|
-
return effect(() => {
|
|
40
|
-
const formValue = form.value.value;
|
|
41
|
-
withDebounce(() => {
|
|
42
|
-
const shouldEnable = condition(formValue);
|
|
43
|
-
if (shouldEnable) {
|
|
44
|
-
targetNode.enable();
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
targetNode.disable();
|
|
48
|
-
if (resetOnDisable) {
|
|
49
|
-
targetNode.reset();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
-
getCurrentBehaviorRegistry().register(handler, { debounce });
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Условное выключение поля (инверсия enableWhen)
|
|
60
|
-
*
|
|
61
|
-
* @group Behaviors
|
|
62
|
-
* @category Behavior Rules
|
|
63
|
-
*
|
|
64
|
-
* @param field - Поле для выключения
|
|
65
|
-
* @param condition - Функция условия (true = disable, false = enable)
|
|
66
|
-
* @param options - Опции
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
71
|
-
* // Выключить поле для потребительского кредита
|
|
72
|
-
* disableWhen(path.propertyValue, (form) => form.loanType === 'consumer');
|
|
73
|
-
* };
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
export function disableWhen(
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
field, condition, options) {
|
|
79
|
-
// Инвертируем условие
|
|
80
|
-
enableWhen(field, (form) => !condition(form), options);
|
|
81
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
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 } from './reset-when';
|
|
11
|
-
export { transformValue, createTransformer, transformers, } from './transform-value';
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
}
|