@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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/behaviors.d.ts +2 -0
  4. package/dist/behaviors.js +230 -0
  5. package/dist/core/behavior/behavior-applicator.d.ts +71 -0
  6. package/dist/core/behavior/behavior-applicator.js +92 -0
  7. package/dist/core/behavior/behavior-context.d.ts +29 -0
  8. package/dist/core/behavior/behavior-context.js +38 -0
  9. package/dist/core/behavior/behavior-registry.d.ts +97 -0
  10. package/dist/core/behavior/behavior-registry.js +198 -0
  11. package/dist/core/behavior/behaviors/compute-from.d.ts +41 -0
  12. package/dist/core/behavior/behaviors/compute-from.js +84 -0
  13. package/dist/core/behavior/behaviors/copy-from.d.ts +31 -0
  14. package/dist/core/behavior/behaviors/copy-from.js +64 -0
  15. package/dist/core/behavior/behaviors/enable-when.d.ts +49 -0
  16. package/dist/core/behavior/behaviors/enable-when.js +81 -0
  17. package/dist/core/behavior/behaviors/index.d.ts +11 -0
  18. package/dist/core/behavior/behaviors/index.js +11 -0
  19. package/dist/core/behavior/behaviors/reset-when.d.ts +51 -0
  20. package/dist/core/behavior/behaviors/reset-when.js +63 -0
  21. package/dist/core/behavior/behaviors/revalidate-when.d.ts +30 -0
  22. package/dist/core/behavior/behaviors/revalidate-when.js +51 -0
  23. package/dist/core/behavior/behaviors/sync-fields.d.ts +28 -0
  24. package/dist/core/behavior/behaviors/sync-fields.js +66 -0
  25. package/dist/core/behavior/behaviors/transform-value.d.ts +120 -0
  26. package/dist/core/behavior/behaviors/transform-value.js +110 -0
  27. package/dist/core/behavior/behaviors/watch-field.d.ts +35 -0
  28. package/dist/core/behavior/behaviors/watch-field.js +56 -0
  29. package/dist/core/behavior/compose-behavior.d.ts +106 -0
  30. package/dist/core/behavior/compose-behavior.js +166 -0
  31. package/dist/core/behavior/create-field-path.d.ts +20 -0
  32. package/dist/core/behavior/create-field-path.js +69 -0
  33. package/dist/core/behavior/index.d.ts +12 -0
  34. package/dist/core/behavior/index.js +17 -0
  35. package/dist/core/behavior/types.d.ts +152 -0
  36. package/dist/core/behavior/types.js +7 -0
  37. package/dist/core/context/form-context-impl.d.ts +29 -0
  38. package/dist/core/context/form-context-impl.js +37 -0
  39. package/dist/core/factories/index.d.ts +6 -0
  40. package/dist/core/factories/index.js +6 -0
  41. package/dist/core/factories/node-factory.d.ts +209 -0
  42. package/dist/core/factories/node-factory.js +281 -0
  43. package/dist/core/nodes/array-node.d.ts +308 -0
  44. package/dist/core/nodes/array-node.js +534 -0
  45. package/dist/core/nodes/field-node.d.ts +269 -0
  46. package/dist/core/nodes/field-node.js +510 -0
  47. package/dist/core/nodes/form-node.d.ts +342 -0
  48. package/dist/core/nodes/form-node.js +343 -0
  49. package/dist/core/nodes/group-node/field-registry.d.ts +191 -0
  50. package/dist/core/nodes/group-node/field-registry.js +215 -0
  51. package/dist/core/nodes/group-node/index.d.ts +11 -0
  52. package/dist/core/nodes/group-node/index.js +11 -0
  53. package/dist/core/nodes/group-node/proxy-builder.d.ts +71 -0
  54. package/dist/core/nodes/group-node/proxy-builder.js +161 -0
  55. package/dist/core/nodes/group-node/state-manager.d.ts +184 -0
  56. package/dist/core/nodes/group-node/state-manager.js +265 -0
  57. package/dist/core/nodes/group-node.d.ts +494 -0
  58. package/dist/core/nodes/group-node.js +770 -0
  59. package/dist/core/types/deep-schema.d.ts +78 -0
  60. package/dist/core/types/deep-schema.js +11 -0
  61. package/dist/core/types/field-path.d.ts +42 -0
  62. package/dist/core/types/field-path.js +4 -0
  63. package/dist/core/types/form-context.d.ts +83 -0
  64. package/dist/core/types/form-context.js +25 -0
  65. package/dist/core/types/group-node-proxy.d.ts +135 -0
  66. package/dist/core/types/group-node-proxy.js +31 -0
  67. package/dist/core/types/index.d.ts +163 -0
  68. package/dist/core/types/index.js +4 -0
  69. package/dist/core/types/validation-schema.d.ts +104 -0
  70. package/dist/core/types/validation-schema.js +10 -0
  71. package/dist/core/utils/create-form.d.ts +61 -0
  72. package/dist/core/utils/create-form.js +24 -0
  73. package/dist/core/utils/debounce.d.ts +160 -0
  74. package/dist/core/utils/debounce.js +197 -0
  75. package/dist/core/utils/error-handler.d.ts +180 -0
  76. package/dist/core/utils/error-handler.js +226 -0
  77. package/dist/core/utils/field-path-navigator.d.ts +240 -0
  78. package/dist/core/utils/field-path-navigator.js +374 -0
  79. package/dist/core/utils/index.d.ts +14 -0
  80. package/dist/core/utils/index.js +14 -0
  81. package/dist/core/utils/registry-helpers.d.ts +50 -0
  82. package/dist/core/utils/registry-helpers.js +79 -0
  83. package/dist/core/utils/registry-stack.d.ts +69 -0
  84. package/dist/core/utils/registry-stack.js +86 -0
  85. package/dist/core/utils/resources.d.ts +41 -0
  86. package/dist/core/utils/resources.js +69 -0
  87. package/dist/core/utils/subscription-manager.d.ts +180 -0
  88. package/dist/core/utils/subscription-manager.js +214 -0
  89. package/dist/core/utils/type-guards.d.ts +116 -0
  90. package/dist/core/utils/type-guards.js +169 -0
  91. package/dist/core/validation/core/apply-when.d.ts +28 -0
  92. package/dist/core/validation/core/apply-when.js +41 -0
  93. package/dist/core/validation/core/apply.d.ts +63 -0
  94. package/dist/core/validation/core/apply.js +38 -0
  95. package/dist/core/validation/core/index.d.ts +8 -0
  96. package/dist/core/validation/core/index.js +8 -0
  97. package/dist/core/validation/core/validate-async.d.ts +42 -0
  98. package/dist/core/validation/core/validate-async.js +45 -0
  99. package/dist/core/validation/core/validate-tree.d.ts +35 -0
  100. package/dist/core/validation/core/validate-tree.js +37 -0
  101. package/dist/core/validation/core/validate.d.ts +32 -0
  102. package/dist/core/validation/core/validate.js +38 -0
  103. package/dist/core/validation/field-path.d.ts +43 -0
  104. package/dist/core/validation/field-path.js +147 -0
  105. package/dist/core/validation/index.d.ts +21 -0
  106. package/dist/core/validation/index.js +33 -0
  107. package/dist/core/validation/validate-form.d.ts +85 -0
  108. package/dist/core/validation/validate-form.js +152 -0
  109. package/dist/core/validation/validation-applicator.d.ts +89 -0
  110. package/dist/core/validation/validation-applicator.js +217 -0
  111. package/dist/core/validation/validation-context.d.ts +47 -0
  112. package/dist/core/validation/validation-context.js +75 -0
  113. package/dist/core/validation/validation-registry.d.ts +156 -0
  114. package/dist/core/validation/validation-registry.js +298 -0
  115. package/dist/core/validation/validators/array-validators.d.ts +63 -0
  116. package/dist/core/validation/validators/array-validators.js +86 -0
  117. package/dist/core/validation/validators/date.d.ts +38 -0
  118. package/dist/core/validation/validators/date.js +117 -0
  119. package/dist/core/validation/validators/email.d.ts +44 -0
  120. package/dist/core/validation/validators/email.js +60 -0
  121. package/dist/core/validation/validators/index.d.ts +14 -0
  122. package/dist/core/validation/validators/index.js +14 -0
  123. package/dist/core/validation/validators/max-length.d.ts +45 -0
  124. package/dist/core/validation/validators/max-length.js +60 -0
  125. package/dist/core/validation/validators/max.d.ts +45 -0
  126. package/dist/core/validation/validators/max.js +60 -0
  127. package/dist/core/validation/validators/min-length.d.ts +45 -0
  128. package/dist/core/validation/validators/min-length.js +60 -0
  129. package/dist/core/validation/validators/min.d.ts +45 -0
  130. package/dist/core/validation/validators/min.js +60 -0
  131. package/dist/core/validation/validators/number.d.ts +38 -0
  132. package/dist/core/validation/validators/number.js +90 -0
  133. package/dist/core/validation/validators/pattern.d.ts +47 -0
  134. package/dist/core/validation/validators/pattern.js +62 -0
  135. package/dist/core/validation/validators/phone.d.ts +34 -0
  136. package/dist/core/validation/validators/phone.js +58 -0
  137. package/dist/core/validation/validators/required.d.ts +48 -0
  138. package/dist/core/validation/validators/required.js +69 -0
  139. package/dist/core/validation/validators/url.d.ts +29 -0
  140. package/dist/core/validation/validators/url.js +55 -0
  141. package/dist/create-field-path-CdPF3lIK.js +704 -0
  142. package/dist/hooks/useFormControl.d.ts +48 -0
  143. package/dist/hooks/useFormControl.js +298 -0
  144. package/dist/index.d.ts +10 -0
  145. package/dist/index.js +8 -0
  146. package/dist/node-factory-D7DOnSSN.js +3200 -0
  147. package/dist/validators.d.ts +2 -0
  148. package/dist/validators.js +298 -0
  149. package/llms.txt +847 -0
  150. 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;