@reformer/core 1.1.0 → 2.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/dist/behaviors-DzYL8kY_.js +499 -0
- package/dist/behaviors.d.ts +6 -2
- package/dist/behaviors.js +19 -227
- package/dist/core/behavior/behavior-context.d.ts +6 -2
- package/dist/core/behavior/create-field-path.d.ts +3 -16
- package/dist/core/nodes/group-node.d.ts +14 -193
- package/dist/core/types/form-context.d.ts +10 -4
- package/dist/core/utils/field-path.d.ts +48 -0
- package/dist/core/utils/index.d.ts +1 -0
- package/dist/core/validation/core/validate-tree.d.ts +10 -4
- package/dist/core/validation/field-path.d.ts +3 -39
- package/dist/core/validation/validation-context.d.ts +23 -0
- package/dist/hooks/types.d.ts +328 -0
- package/dist/hooks/useFormControl.d.ts +13 -37
- package/dist/hooks/useFormControlValue.d.ts +167 -0
- package/dist/hooks/useSignalSubscription.d.ts +17 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2886 -8
- package/dist/{create-field-path-CdPF3lIK.js → registry-helpers-BRxAr6nG.js} +133 -347
- package/dist/validators-gXoHPdqM.js +418 -0
- package/dist/validators.d.ts +6 -2
- package/dist/validators.js +29 -296
- package/llms.txt +1283 -22
- package/package.json +8 -4
- package/dist/core/behavior/behavior-applicator.d.ts +0 -71
- package/dist/core/behavior/behavior-applicator.js +0 -92
- package/dist/core/behavior/behavior-context.js +0 -38
- 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.d.ts +0 -191
- package/dist/core/nodes/group-node/field-registry.js +0 -215
- package/dist/core/nodes/group-node/index.d.ts +0 -11
- package/dist/core/nodes/group-node/index.js +0 -11
- package/dist/core/nodes/group-node/proxy-builder.d.ts +0 -71
- package/dist/core/nodes/group-node/proxy-builder.js +0 -161
- package/dist/core/nodes/group-node/state-manager.d.ts +0 -184
- 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 -37
- 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/hooks/useFormControl.js +0 -298
- package/dist/node-factory-D7DOnSSN.js +0 -3200
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reformer/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.3",
|
|
4
4
|
"description": "Reactive form state management library for React with signals-based architecture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"sideEffects": false,
|
|
24
24
|
"scripts": {
|
|
25
|
-
"
|
|
25
|
+
"generate:llms": "npx tsx scripts/generate-llms.ts",
|
|
26
|
+
"build": "npm run generate:llms && vite build && tsc -p tsconfig.json",
|
|
26
27
|
"dev": "vite",
|
|
27
28
|
"test": "vitest",
|
|
28
29
|
"test:watch": "vitest watch"
|
|
@@ -63,15 +64,18 @@
|
|
|
63
64
|
"LLMs.txt"
|
|
64
65
|
],
|
|
65
66
|
"peerDependencies": {
|
|
66
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
67
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
67
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
68
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
70
71
|
"@preact/signals-core": "^1.8.0",
|
|
72
|
+
"use-sync-external-store": "^1.2.0",
|
|
71
73
|
"uuid": "^13.0.0"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
76
|
+
"@types/use-sync-external-store": "^0.0.6",
|
|
74
77
|
"@types/node": "^24.10.1",
|
|
78
|
+
"tsx": "^4.19.2",
|
|
75
79
|
"@types/react": "^19.2.7",
|
|
76
80
|
"@types/react-dom": "^19.2.3",
|
|
77
81
|
"@types/uuid": "^10.0.0",
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Применение behavior схемы к форме
|
|
3
|
-
*
|
|
4
|
-
* Извлечено из GroupNode для соблюдения SRP (Single Responsibility Principle).
|
|
5
|
-
* Управляет процессом регистрации и применения behaviors.
|
|
6
|
-
*
|
|
7
|
-
* @template T Тип формы
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* class GroupNode {
|
|
12
|
-
* private readonly behaviorApplicator = new BehaviorApplicator(this);
|
|
13
|
-
*
|
|
14
|
-
* applyBehaviorSchema(schemaFn: BehaviorSchemaFn<T>): () => void {
|
|
15
|
-
* return this.behaviorApplicator.apply(schemaFn);
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
import type { GroupNode } from '../nodes/group-node';
|
|
21
|
-
import type { BehaviorSchemaFn } from './types';
|
|
22
|
-
import { BehaviorRegistry } from './behavior-registry';
|
|
23
|
-
/**
|
|
24
|
-
* Класс для применения behavior схемы к форме
|
|
25
|
-
*
|
|
26
|
-
* Выполняет:
|
|
27
|
-
* 1. Начало регистрации behaviors (beginRegistration)
|
|
28
|
-
* 2. Выполнение схемы (регистрация behaviors)
|
|
29
|
-
* 3. Завершение регистрации (endRegistration) - применение behaviors
|
|
30
|
-
* 4. Возврат функции cleanup для отписки
|
|
31
|
-
*
|
|
32
|
-
* @template T Тип формы (объект)
|
|
33
|
-
*/
|
|
34
|
-
export declare class BehaviorApplicator<T> {
|
|
35
|
-
private readonly form;
|
|
36
|
-
private readonly behaviorRegistry;
|
|
37
|
-
constructor(form: GroupNode<T>, behaviorRegistry: BehaviorRegistry);
|
|
38
|
-
/**
|
|
39
|
-
* Применить behavior схему к форме
|
|
40
|
-
*
|
|
41
|
-
* Этапы:
|
|
42
|
-
* 1. Начать регистрацию (beginRegistration)
|
|
43
|
-
* 2. Выполнить схему (регистрация behaviors)
|
|
44
|
-
* 3. Завершить регистрацию (endRegistration) - применить behaviors
|
|
45
|
-
* 4. Вернуть функцию cleanup для отписки
|
|
46
|
-
*
|
|
47
|
-
* @param schemaFn Функция-схема behavior
|
|
48
|
-
* @returns Функция отписки от всех behaviors
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* const cleanup = behaviorApplicator.apply((path) => {
|
|
53
|
-
* copyFrom(path.residenceAddress, path.registrationAddress, {
|
|
54
|
-
* when: (form) => form.sameAsRegistration === true
|
|
55
|
-
* });
|
|
56
|
-
*
|
|
57
|
-
* enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage');
|
|
58
|
-
*
|
|
59
|
-
* computeFrom(
|
|
60
|
-
* path.initialPayment,
|
|
61
|
-
* [path.propertyValue],
|
|
62
|
-
* (propertyValue) => propertyValue ? propertyValue * 0.2 : null
|
|
63
|
-
* );
|
|
64
|
-
* });
|
|
65
|
-
*
|
|
66
|
-
* // Cleanup при unmount
|
|
67
|
-
* useEffect(() => cleanup, []);
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
apply(schemaFn: BehaviorSchemaFn<T>): () => void;
|
|
71
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Применение behavior схемы к форме
|
|
3
|
-
*
|
|
4
|
-
* Извлечено из GroupNode для соблюдения SRP (Single Responsibility Principle).
|
|
5
|
-
* Управляет процессом регистрации и применения behaviors.
|
|
6
|
-
*
|
|
7
|
-
* @template T Тип формы
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* class GroupNode {
|
|
12
|
-
* private readonly behaviorApplicator = new BehaviorApplicator(this);
|
|
13
|
-
*
|
|
14
|
-
* applyBehaviorSchema(schemaFn: BehaviorSchemaFn<T>): () => void {
|
|
15
|
-
* return this.behaviorApplicator.apply(schemaFn);
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
import { createFieldPath as createBehaviorFieldPath } from './create-field-path';
|
|
21
|
-
import { FormErrorHandler, ErrorStrategy } from '../utils/error-handler';
|
|
22
|
-
/**
|
|
23
|
-
* Класс для применения behavior схемы к форме
|
|
24
|
-
*
|
|
25
|
-
* Выполняет:
|
|
26
|
-
* 1. Начало регистрации behaviors (beginRegistration)
|
|
27
|
-
* 2. Выполнение схемы (регистрация behaviors)
|
|
28
|
-
* 3. Завершение регистрации (endRegistration) - применение behaviors
|
|
29
|
-
* 4. Возврат функции cleanup для отписки
|
|
30
|
-
*
|
|
31
|
-
* @template T Тип формы (объект)
|
|
32
|
-
*/
|
|
33
|
-
export class BehaviorApplicator {
|
|
34
|
-
form;
|
|
35
|
-
behaviorRegistry;
|
|
36
|
-
constructor(form, behaviorRegistry) {
|
|
37
|
-
this.form = form;
|
|
38
|
-
this.behaviorRegistry = behaviorRegistry;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Применить behavior схему к форме
|
|
42
|
-
*
|
|
43
|
-
* Этапы:
|
|
44
|
-
* 1. Начать регистрацию (beginRegistration)
|
|
45
|
-
* 2. Выполнить схему (регистрация behaviors)
|
|
46
|
-
* 3. Завершить регистрацию (endRegistration) - применить behaviors
|
|
47
|
-
* 4. Вернуть функцию cleanup для отписки
|
|
48
|
-
*
|
|
49
|
-
* @param schemaFn Функция-схема behavior
|
|
50
|
-
* @returns Функция отписки от всех behaviors
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* const cleanup = behaviorApplicator.apply((path) => {
|
|
55
|
-
* copyFrom(path.residenceAddress, path.registrationAddress, {
|
|
56
|
-
* when: (form) => form.sameAsRegistration === true
|
|
57
|
-
* });
|
|
58
|
-
*
|
|
59
|
-
* enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage');
|
|
60
|
-
*
|
|
61
|
-
* computeFrom(
|
|
62
|
-
* path.initialPayment,
|
|
63
|
-
* [path.propertyValue],
|
|
64
|
-
* (propertyValue) => propertyValue ? propertyValue * 0.2 : null
|
|
65
|
-
* );
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
|
-
* // Cleanup при unmount
|
|
69
|
-
* useEffect(() => cleanup, []);
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
apply(schemaFn) {
|
|
73
|
-
this.behaviorRegistry.beginRegistration();
|
|
74
|
-
try {
|
|
75
|
-
// 1. Создать field path для type-safe доступа к полям
|
|
76
|
-
const path = createBehaviorFieldPath();
|
|
77
|
-
// 2. Выполнить схему (регистрация behaviors)
|
|
78
|
-
schemaFn(path);
|
|
79
|
-
// 3. Завершить регистрацию и применить behaviors
|
|
80
|
-
// Используем публичный метод getProxy() для получения proxy-инстанса
|
|
81
|
-
const formToUse = this.form.getProxy();
|
|
82
|
-
const result = this.behaviorRegistry.endRegistration(formToUse);
|
|
83
|
-
// 4. Вернуть функцию cleanup
|
|
84
|
-
return result.cleanup;
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
FormErrorHandler.handle(error, 'BehaviorApplicator', ErrorStrategy.THROW);
|
|
88
|
-
// TypeScript требует return, но код никогда не дойдет сюда
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BehaviorContext - контекст для behavior callback функций
|
|
3
|
-
*
|
|
4
|
-
* Реализует FormContext для behavior схем
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Реализация BehaviorContext (FormContext)
|
|
8
|
-
*
|
|
9
|
-
* Предоставляет:
|
|
10
|
-
* - `form` - прямой типизированный доступ к форме
|
|
11
|
-
* - `setFieldValue` - безопасная установка значения (emitEvent: false)
|
|
12
|
-
*/
|
|
13
|
-
export class BehaviorContextImpl {
|
|
14
|
-
/**
|
|
15
|
-
* Форма с типизированным Proxy-доступом к полям
|
|
16
|
-
*/
|
|
17
|
-
form;
|
|
18
|
-
_form;
|
|
19
|
-
constructor(form) {
|
|
20
|
-
this._form = form;
|
|
21
|
-
// Используем _proxyInstance если доступен, иначе fallback на form
|
|
22
|
-
const proxy = (form
|
|
23
|
-
._proxyInstance || form);
|
|
24
|
-
this.form = proxy;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Безопасно установить значение поля по строковому пути
|
|
28
|
-
*
|
|
29
|
-
* Автоматически использует emitEvent: false для предотвращения циклов
|
|
30
|
-
*/
|
|
31
|
-
setFieldValue(path, value) {
|
|
32
|
-
const node = this._form.getFieldByPath(path);
|
|
33
|
-
if (node) {
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
node.setValue(value, { emitEvent: false });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
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 схемы к разным формам одновременно
|
|
@@ -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';
|