@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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BehaviorRegistry - регистрация и управление behavior схемами
|
|
3
|
+
*
|
|
4
|
+
* Аналогично ValidationRegistry, но для реактивного поведения форм
|
|
5
|
+
*/
|
|
6
|
+
import { BehaviorContextImpl } from './behavior-context';
|
|
7
|
+
import { RegistryStack } from '../utils/registry-stack';
|
|
8
|
+
/**
|
|
9
|
+
* Реестр behaviors для формы
|
|
10
|
+
*
|
|
11
|
+
* Каждый экземпляр GroupNode создает собственный реестр (композиция).
|
|
12
|
+
* Устраняет race conditions и изолирует формы друг от друга.
|
|
13
|
+
*
|
|
14
|
+
* Context stack используется для tracking текущего активного реестра:
|
|
15
|
+
* - beginRegistration() помещает this в stack
|
|
16
|
+
* - endRegistration() извлекает из stack
|
|
17
|
+
* - getCurrent() возвращает текущий активный реестр
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* class GroupNode {
|
|
22
|
+
* private readonly behaviorRegistry = new BehaviorRegistry();
|
|
23
|
+
*
|
|
24
|
+
* applyBehaviorSchema(schemaFn) {
|
|
25
|
+
* this.behaviorRegistry.beginRegistration(); // Pushes this to stack
|
|
26
|
+
* schemaFn(createBehaviorFieldPath(this)); // Uses getCurrent()
|
|
27
|
+
* return this.behaviorRegistry.endRegistration(this); // Pops from stack
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class BehaviorRegistry {
|
|
33
|
+
/**
|
|
34
|
+
* Stack активных контекстов регистрации
|
|
35
|
+
* Используется для изоляции форм друг от друга
|
|
36
|
+
*/
|
|
37
|
+
static contextStack = new RegistryStack();
|
|
38
|
+
registrations = [];
|
|
39
|
+
isRegistering = false;
|
|
40
|
+
/**
|
|
41
|
+
* Получить текущий активный реестр из context stack
|
|
42
|
+
*
|
|
43
|
+
* @returns Текущий активный реестр или null
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // В schema-behaviors.ts
|
|
48
|
+
* export function copyFrom(...) {
|
|
49
|
+
* const registry = BehaviorRegistry.getCurrent();
|
|
50
|
+
* if (registry) {
|
|
51
|
+
* registry.register({ ... });
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
static getCurrent() {
|
|
57
|
+
return BehaviorRegistry.contextStack.getCurrent();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Начать регистрацию behaviors
|
|
61
|
+
* Вызывается перед применением схемы
|
|
62
|
+
*
|
|
63
|
+
* Помещает this в context stack для изоляции форм
|
|
64
|
+
*/
|
|
65
|
+
beginRegistration() {
|
|
66
|
+
this.isRegistering = true;
|
|
67
|
+
this.registrations = [];
|
|
68
|
+
// Помещаем this в stack для tracking текущего активного реестра
|
|
69
|
+
BehaviorRegistry.contextStack.push(this);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Зарегистрировать behavior handler
|
|
73
|
+
* Вызывается функциями из schema-behaviors.ts
|
|
74
|
+
*
|
|
75
|
+
* @param handler - BehaviorHandlerFn функция
|
|
76
|
+
* @param options - Опции behavior (debounce)
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const handler = createCopyBehavior(target, source, { when: ... });
|
|
81
|
+
* registry.register(handler, { debounce: 300 });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
register(handler, options) {
|
|
85
|
+
if (!this.isRegistering) {
|
|
86
|
+
if (import.meta.env.DEV) {
|
|
87
|
+
throw new Error('BehaviorRegistry: call beginRegistration() before registering behaviors');
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.registrations.push({
|
|
92
|
+
// Type assertion безопасен: handler будет вызван с правильным типом формы в createEffect
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
+
handler: handler,
|
|
95
|
+
debounce: options?.debounce,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Завершить регистрацию и применить behaviors к форме
|
|
100
|
+
* Создает effect подписки для всех зарегистрированных behaviors
|
|
101
|
+
*
|
|
102
|
+
* Извлекает this из context stack
|
|
103
|
+
*
|
|
104
|
+
* @param form - GroupNode формы
|
|
105
|
+
* @returns Количество зарегистрированных behaviors и функция cleanup
|
|
106
|
+
*/
|
|
107
|
+
endRegistration(form) {
|
|
108
|
+
this.isRegistering = false;
|
|
109
|
+
// Извлекаем из stack с проверкой
|
|
110
|
+
BehaviorRegistry.contextStack.verify(this, 'BehaviorRegistry');
|
|
111
|
+
const context = new BehaviorContextImpl(form);
|
|
112
|
+
const disposeCallbacks = [];
|
|
113
|
+
// Создаем effect подписки для каждого behavior
|
|
114
|
+
for (const registered of this.registrations) {
|
|
115
|
+
const dispose = this.createEffect(registered, form, context);
|
|
116
|
+
if (dispose) {
|
|
117
|
+
disposeCallbacks.push(dispose);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Функция cleanup для отписки от всех effects
|
|
121
|
+
const cleanup = () => {
|
|
122
|
+
disposeCallbacks.forEach((dispose) => dispose());
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
count: this.registrations.length,
|
|
126
|
+
cleanup,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Создать effect подписку для behavior
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
createEffect(registered, form, context) {
|
|
134
|
+
const { handler, debounce: debounceMs = 0 } = registered;
|
|
135
|
+
let debounceTimer = null;
|
|
136
|
+
// Обертка для debounce
|
|
137
|
+
const withDebounce = (callback) => {
|
|
138
|
+
if (debounceMs > 0) {
|
|
139
|
+
if (debounceTimer)
|
|
140
|
+
clearTimeout(debounceTimer);
|
|
141
|
+
debounceTimer = setTimeout(callback, debounceMs);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
callback();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
// Cleanup функция для debounce таймера
|
|
148
|
+
const cleanupDebounce = () => {
|
|
149
|
+
if (debounceTimer) {
|
|
150
|
+
clearTimeout(debounceTimer);
|
|
151
|
+
debounceTimer = null;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
// Вызываем handler напрямую
|
|
155
|
+
// Type assertion необходим из-за contravariance: handler хранится как
|
|
156
|
+
// BehaviorHandlerFn<FormFields>, но вызывается с более специфичным типом T.
|
|
157
|
+
// Используем any для обхода ограничений TypeScript при хранении generic handlers в массиве.
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
const effectDispose = handler(form, context, withDebounce);
|
|
160
|
+
if (!effectDispose) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// Возвращаем комбинированный cleanup
|
|
164
|
+
// который очищает и effect, и debounce таймер
|
|
165
|
+
return () => {
|
|
166
|
+
cleanupDebounce();
|
|
167
|
+
if (effectDispose) {
|
|
168
|
+
effectDispose();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// Глобальный экземпляр BehaviorRegistry УДАЛЕН
|
|
175
|
+
// ============================================================================
|
|
176
|
+
//
|
|
177
|
+
// Ранее здесь был глобальный Singleton экземпляр BehaviorRegistry,
|
|
178
|
+
// который создавал race conditions и нарушал изоляцию форм.
|
|
179
|
+
//
|
|
180
|
+
// Теперь каждый GroupNode создает собственный экземпляр BehaviorRegistry:
|
|
181
|
+
//
|
|
182
|
+
// @example
|
|
183
|
+
// ```typescript
|
|
184
|
+
// class GroupNode {
|
|
185
|
+
// private readonly behaviorRegistry = new BehaviorRegistry();
|
|
186
|
+
//
|
|
187
|
+
// applyBehaviorSchema(schemaFn) {
|
|
188
|
+
// this.behaviorRegistry.beginRegistration();
|
|
189
|
+
// schemaFn(createBehaviorFieldPath(this));
|
|
190
|
+
// return this.behaviorRegistry.endRegistration(this);
|
|
191
|
+
// }
|
|
192
|
+
// }
|
|
193
|
+
// ```
|
|
194
|
+
//
|
|
195
|
+
// Это обеспечивает:
|
|
196
|
+
// - Полную изоляцию форм друг от друга
|
|
197
|
+
// - Отсутствие race conditions при параллельной регистрации
|
|
198
|
+
// - Возможность применять разные behavior схемы к разным формам одновременно
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Вычисляемые поля
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/computeFrom
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode } from '../../types';
|
|
9
|
+
import type { ComputeFromOptions } from '../types';
|
|
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 declare function computeFrom<TForm, TTarget>(sources: FieldPathNode<TForm, any>[], target: FieldPathNode<TForm, TTarget>, computeFn: (values: TForm) => TTarget, options?: ComputeFromOptions<TForm>): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Копирование значений между полями
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/copyFrom
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode } from '../../types';
|
|
9
|
+
import type { CopyFromOptions } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Копирует значения из одного поля/группы в другое при выполнении условия
|
|
12
|
+
*
|
|
13
|
+
* @group Behaviors
|
|
14
|
+
* @category Behavior Rules
|
|
15
|
+
*
|
|
16
|
+
* @param source - Откуда копировать
|
|
17
|
+
* @param target - Куда копировать
|
|
18
|
+
* @param options - Опции копирования
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
23
|
+
* // Копировать адрес регистрации в адрес проживания
|
|
24
|
+
* copyFrom(path.registrationAddress, path.residenceAddress, {
|
|
25
|
+
* when: (form) => form.sameAsRegistration === true,
|
|
26
|
+
* fields: 'all'
|
|
27
|
+
* });
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function copyFrom<TForm, TSource, TTarget>(source: FieldPathNode<TForm, TSource>, target: FieldPathNode<TForm, TTarget>, options?: CopyFromOptions<TSource, TForm>): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Условное включение/отключение полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/enableWhen
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode } from '../../types';
|
|
9
|
+
import type { EnableWhenOptions } from '../types';
|
|
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 declare function enableWhen<TForm>(field: FieldPathNode<TForm, any>, condition: (form: TForm) => boolean, options?: EnableWhenOptions): void;
|
|
31
|
+
/**
|
|
32
|
+
* Условное выключение поля (инверсия enableWhen)
|
|
33
|
+
*
|
|
34
|
+
* @group Behaviors
|
|
35
|
+
* @category Behavior Rules
|
|
36
|
+
*
|
|
37
|
+
* @param field - Поле для выключения
|
|
38
|
+
* @param condition - Функция условия (true = disable, false = enable)
|
|
39
|
+
* @param options - Опции
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
44
|
+
* // Выключить поле для потребительского кредита
|
|
45
|
+
* disableWhen(path.propertyValue, (form) => form.loanType === 'consumer');
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function disableWhen<TForm>(field: FieldPathNode<TForm, any>, condition: (form: TForm) => boolean, options?: EnableWhenOptions): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
}
|
|
@@ -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,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 } from './reset-when';
|
|
11
|
+
export { transformValue, createTransformer, transformers, } from './transform-value';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Условный сброс полей
|
|
3
|
+
*
|
|
4
|
+
* @group Behaviors
|
|
5
|
+
* @category Behavior Rules
|
|
6
|
+
* @module behaviors/resetWhen
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPathNode, FormFields, FormValue } from '../../types';
|
|
9
|
+
/**
|
|
10
|
+
* Опции для resetWhen
|
|
11
|
+
*
|
|
12
|
+
* @group Behaviors
|
|
13
|
+
* @category Behavior Types
|
|
14
|
+
*/
|
|
15
|
+
export interface ResetWhenOptions {
|
|
16
|
+
/** Значение для сброса (по умолчанию null) */
|
|
17
|
+
resetValue?: FormValue;
|
|
18
|
+
/** Сбросить только если поле dirty */
|
|
19
|
+
onlyIfDirty?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Условный сброс поля при выполнении условия
|
|
23
|
+
*
|
|
24
|
+
* @group Behaviors
|
|
25
|
+
* @category Behavior Rules
|
|
26
|
+
*
|
|
27
|
+
* @param field - Поле для сброса
|
|
28
|
+
* @param condition - Функция условия (true = reset)
|
|
29
|
+
* @param options - Опции
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const schema: BehaviorSchemaFn<MyForm> = (path) => {
|
|
34
|
+
* // Сбросить поле при изменении типа кредита
|
|
35
|
+
* resetWhen(path.propertyValue, (form) => form.loanType !== 'mortgage');
|
|
36
|
+
*
|
|
37
|
+
* // Сбросить с кастомным значением
|
|
38
|
+
* resetWhen(path.initialPayment, (form) => !form.propertyValue, {
|
|
39
|
+
* resetValue: 0
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Сбросить только если поле было изменено пользователем
|
|
43
|
+
* resetWhen(path.carPrice, (form) => form.loanType !== 'car', {
|
|
44
|
+
* onlyIfDirty: true
|
|
45
|
+
* });
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function resetWhen<TForm extends FormFields>(field: FieldPathNode<TForm, FormValue>, condition: (form: TForm) => boolean, options?: ResetWhenOptions & {
|
|
50
|
+
debounce?: number;
|
|
51
|
+
}): void;
|