@reformer/core 1.0.0-beta.1
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 +143 -0
- package/dist/behaviors/compute-from.d.ts +2 -0
- package/dist/behaviors/compute-from.js +31 -0
- package/dist/behaviors/copy-from.d.ts +2 -0
- package/dist/behaviors/copy-from.js +29 -0
- package/dist/behaviors/enable-when.d.ts +2 -0
- package/dist/behaviors/enable-when.js +25 -0
- package/dist/behaviors/reset-when.d.ts +2 -0
- package/dist/behaviors/reset-when.js +24 -0
- package/dist/behaviors/revalidate-when.d.ts +2 -0
- package/dist/behaviors/revalidate-when.js +18 -0
- package/dist/behaviors/sync-fields.d.ts +2 -0
- package/dist/behaviors/sync-fields.js +41 -0
- package/dist/behaviors/transform-value.d.ts +2 -0
- package/dist/behaviors/transform-value.js +45 -0
- package/dist/behaviors/watch-field.d.ts +2 -0
- package/dist/behaviors/watch-field.js +21 -0
- package/dist/behaviors.d.ts +6 -0
- package/dist/behaviors.js +29 -0
- package/dist/core/behavior/behavior-context.d.ts +47 -0
- package/dist/core/behavior/behavior-registry.d.ts +85 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +70 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +56 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +118 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +64 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +53 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +48 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +192 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +80 -0
- package/dist/core/behavior/compose-behavior.d.ts +96 -0
- package/dist/core/behavior/index.d.ts +11 -0
- package/dist/core/behavior/types.d.ts +146 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/node-factory.d.ts +186 -0
- package/dist/core/nodes/array-node.d.ts +328 -0
- package/dist/core/nodes/field-node.d.ts +294 -0
- package/dist/core/nodes/form-node.d.ts +340 -0
- package/dist/core/nodes/group-node.d.ts +319 -0
- package/dist/core/types/deep-schema.d.ts +68 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/form-context.d.ts +89 -0
- package/dist/core/types/form-proxy.d.ts +105 -0
- package/dist/core/types/index.d.ts +209 -0
- package/dist/core/types/validation-schema.d.ts +95 -0
- package/dist/core/utils/abstract-registry.d.ts +74 -0
- package/dist/core/utils/aggregate-signals.d.ts +71 -0
- package/dist/core/utils/create-form.d.ts +44 -0
- package/dist/core/utils/error-handler.d.ts +163 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path.d.ts +60 -0
- package/dist/core/utils/form-observer.d.ts +176 -0
- package/dist/core/utils/form-proxy-builder.d.ts +25 -0
- package/dist/core/utils/form-submitter.d.ts +121 -0
- package/dist/core/utils/index.d.ts +22 -0
- package/dist/core/utils/registry-helpers.d.ts +43 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/safe-effect.d.ts +73 -0
- package/dist/core/utils/status-machine.d.ts +153 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/type-guards.d.ts +98 -0
- package/dist/core/utils/unique-id.d.ts +53 -0
- package/dist/core/validation/core/apply-when.d.ts +22 -0
- package/dist/core/validation/core/apply.d.ts +52 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/validate-async.d.ts +36 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate.d.ts +26 -0
- package/dist/core/validation/index.d.ts +27 -0
- package/dist/core/validation/validate-form.d.ts +48 -0
- package/dist/core/validation/validation-applicator.d.ts +70 -0
- package/dist/core/validation/validation-context.d.ts +86 -0
- package/dist/core/validation/validation-registry.d.ts +142 -0
- package/dist/core/validation/validators/array-validators.d.ts +53 -0
- package/dist/core/validation/validators/date-utils.d.ts +26 -0
- package/dist/core/validation/validators/email.d.ts +37 -0
- package/dist/core/validation/validators/future-date.d.ts +35 -0
- package/dist/core/validation/validators/index.d.ts +20 -0
- package/dist/core/validation/validators/is-date.d.ts +36 -0
- package/dist/core/validation/validators/max-age.d.ts +36 -0
- package/dist/core/validation/validators/max-date.d.ts +36 -0
- package/dist/core/validation/validators/max-length.d.ts +38 -0
- package/dist/core/validation/validators/max.d.ts +38 -0
- package/dist/core/validation/validators/min-age.d.ts +36 -0
- package/dist/core/validation/validators/min-date.d.ts +36 -0
- package/dist/core/validation/validators/min-length.d.ts +38 -0
- package/dist/core/validation/validators/min.d.ts +38 -0
- package/dist/core/validation/validators/number.d.ts +31 -0
- package/dist/core/validation/validators/past-date.d.ts +35 -0
- package/dist/core/validation/validators/pattern.d.ts +40 -0
- package/dist/core/validation/validators/phone.d.ts +27 -0
- package/dist/core/validation/validators/required.d.ts +41 -0
- package/dist/core/validation/validators/url.d.ts +22 -0
- package/dist/date-utils-xUWFslTj.js +29 -0
- package/dist/field-path-DuKdGcIE.js +66 -0
- package/dist/hooks/types.d.ts +328 -0
- package/dist/hooks/useArrayLength.d.ts +31 -0
- package/dist/hooks/useFormControl.d.ts +24 -0
- package/dist/hooks/useFormControlValue.d.ts +167 -0
- package/dist/hooks/useHiddenCondition.d.ts +25 -0
- package/dist/hooks/useSignalSubscription.d.ts +17 -0
- package/dist/index-D25LsbRm.js +73 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3271 -0
- package/dist/registry-helpers-Bv_BJ1s-.js +615 -0
- package/dist/safe-effect-Dh8uw81c.js +20 -0
- package/dist/validate-C3XiA_zf.js +10 -0
- package/dist/validators/email.d.ts +2 -0
- package/dist/validators/email.js +13 -0
- package/dist/validators/future-date.d.ts +2 -0
- package/dist/validators/future-date.js +20 -0
- package/dist/validators/is-date.d.ts +2 -0
- package/dist/validators/is-date.js +12 -0
- package/dist/validators/max-age.d.ts +2 -0
- package/dist/validators/max-age.js +20 -0
- package/dist/validators/max-date.d.ts +2 -0
- package/dist/validators/max-date.js +20 -0
- package/dist/validators/max-length.d.ts +2 -0
- package/dist/validators/max-length.js +11 -0
- package/dist/validators/max.d.ts +2 -0
- package/dist/validators/max.js +11 -0
- package/dist/validators/min-age.d.ts +2 -0
- package/dist/validators/min-age.js +20 -0
- package/dist/validators/min-date.d.ts +2 -0
- package/dist/validators/min-date.js +20 -0
- package/dist/validators/min-length.d.ts +2 -0
- package/dist/validators/min-length.js +11 -0
- package/dist/validators/min.d.ts +2 -0
- package/dist/validators/min.js +11 -0
- package/dist/validators/number.d.ts +2 -0
- package/dist/validators/number.js +35 -0
- package/dist/validators/past-date.d.ts +2 -0
- package/dist/validators/past-date.js +20 -0
- package/dist/validators/pattern.d.ts +2 -0
- package/dist/validators/pattern.js +11 -0
- package/dist/validators/phone.d.ts +2 -0
- package/dist/validators/phone.js +35 -0
- package/dist/validators/required.d.ts +2 -0
- package/dist/validators/required.js +15 -0
- package/dist/validators/url.d.ts +2 -0
- package/dist/validators/url.js +19 -0
- package/dist/validators-BGsNOgT1.js +207 -0
- package/dist/validators.d.ts +6 -0
- package/dist/validators.js +56 -0
- package/llms.txt +9351 -0
- package/package.json +166 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { FormFields, FormValue, FieldStatus, ValidationError } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Тип события изменения в форме
|
|
4
|
+
*/
|
|
5
|
+
export type FormChangeType = 'value' | 'status' | 'errors' | 'touched' | 'dirty';
|
|
6
|
+
/**
|
|
7
|
+
* Событие изменения в форме
|
|
8
|
+
*/
|
|
9
|
+
export interface FormChangeEvent {
|
|
10
|
+
/** Тип изменения */
|
|
11
|
+
type: FormChangeType;
|
|
12
|
+
/** Путь к полю */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Timestamp события */
|
|
15
|
+
timestamp: number;
|
|
16
|
+
/** Старое значение */
|
|
17
|
+
oldValue?: unknown;
|
|
18
|
+
/** Новое значение */
|
|
19
|
+
newValue: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Интерфейс узла формы для observer
|
|
23
|
+
*/
|
|
24
|
+
export interface ObservableFormNode {
|
|
25
|
+
value: {
|
|
26
|
+
value: FormValue;
|
|
27
|
+
};
|
|
28
|
+
status: {
|
|
29
|
+
value: FieldStatus;
|
|
30
|
+
};
|
|
31
|
+
errors: {
|
|
32
|
+
value: ValidationError[];
|
|
33
|
+
};
|
|
34
|
+
touched: {
|
|
35
|
+
value: boolean;
|
|
36
|
+
};
|
|
37
|
+
dirty: {
|
|
38
|
+
value: boolean;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Интерфейс формы для observer
|
|
43
|
+
*/
|
|
44
|
+
export interface ObservableForm<T extends FormFields> {
|
|
45
|
+
value: {
|
|
46
|
+
value: T;
|
|
47
|
+
};
|
|
48
|
+
status: {
|
|
49
|
+
value: FieldStatus;
|
|
50
|
+
};
|
|
51
|
+
errors: {
|
|
52
|
+
value: ValidationError[];
|
|
53
|
+
};
|
|
54
|
+
touched: {
|
|
55
|
+
value: boolean;
|
|
56
|
+
};
|
|
57
|
+
dirty: {
|
|
58
|
+
value: boolean;
|
|
59
|
+
};
|
|
60
|
+
getFieldByPath(path: string): ObservableFormNode | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Callback для обработки событий изменения
|
|
64
|
+
*/
|
|
65
|
+
export type FormChangeCallback = (event: FormChangeEvent) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Опции для FormObserver
|
|
68
|
+
*/
|
|
69
|
+
export interface FormObserverOptions {
|
|
70
|
+
/** Включить логирование в консоль (по умолчанию true в DEV) */
|
|
71
|
+
enableLogging?: boolean;
|
|
72
|
+
/** Фильтр типов событий */
|
|
73
|
+
eventTypes?: FormChangeType[];
|
|
74
|
+
/** Фильтр путей полей (regex или массив) */
|
|
75
|
+
pathFilter?: RegExp | string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* FormObserver - мониторинг изменений в форме
|
|
79
|
+
*
|
|
80
|
+
* Полезен для:
|
|
81
|
+
* - Отладки сложных форм
|
|
82
|
+
* - Логирования изменений для аудита
|
|
83
|
+
* - Синхронизации с внешними системами
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const observer = new FormObserver(form, {
|
|
88
|
+
* enableLogging: true,
|
|
89
|
+
* eventTypes: ['value', 'errors']
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // Подписка на события
|
|
93
|
+
* const unsubscribe = observer.subscribe((event) => {
|
|
94
|
+
* console.log(`${event.type} at ${event.path}:`, event.newValue);
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Включить трассировку
|
|
98
|
+
* const disposeTracing = observer.enableTracing();
|
|
99
|
+
*
|
|
100
|
+
* // Cleanup
|
|
101
|
+
* unsubscribe();
|
|
102
|
+
* disposeTracing();
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare class FormObserver<T extends FormFields> {
|
|
106
|
+
private readonly form;
|
|
107
|
+
private listeners;
|
|
108
|
+
private disposers;
|
|
109
|
+
private options;
|
|
110
|
+
/**
|
|
111
|
+
* @param form - Форма для наблюдения
|
|
112
|
+
* @param options - Опции observer
|
|
113
|
+
*/
|
|
114
|
+
constructor(form: ObservableForm<T>, options?: FormObserverOptions);
|
|
115
|
+
/**
|
|
116
|
+
* Подписаться на события изменения
|
|
117
|
+
*
|
|
118
|
+
* @param callback - Функция обработки события
|
|
119
|
+
* @returns Функция отписки
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const unsubscribe = observer.subscribe((event) => {
|
|
124
|
+
* // Отправить событие в analytics
|
|
125
|
+
* analytics.track('form_change', event);
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
subscribe(callback: FormChangeCallback): () => void;
|
|
130
|
+
/**
|
|
131
|
+
* Включить трассировку формы
|
|
132
|
+
*
|
|
133
|
+
* Подписывается на изменения основных сигналов формы
|
|
134
|
+
* и вызывает listeners при каждом изменении
|
|
135
|
+
*
|
|
136
|
+
* @returns Функция для отключения трассировки
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const dispose = observer.enableTracing();
|
|
141
|
+
*
|
|
142
|
+
* // Позже, для отключения
|
|
143
|
+
* dispose();
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
enableTracing(): () => void;
|
|
147
|
+
/**
|
|
148
|
+
* Наблюдать за конкретным полем
|
|
149
|
+
*
|
|
150
|
+
* @param path - Путь к полю
|
|
151
|
+
* @returns Функция для отключения наблюдения
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* // Наблюдать за полем email
|
|
156
|
+
* const dispose = observer.watchField('email');
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
watchField(path: string): () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Отправить событие всем listeners
|
|
162
|
+
*/
|
|
163
|
+
private emit;
|
|
164
|
+
/**
|
|
165
|
+
* Проверить, нужно ли отслеживать тип события
|
|
166
|
+
*/
|
|
167
|
+
private shouldTrack;
|
|
168
|
+
/**
|
|
169
|
+
* Проверить, соответствует ли путь фильтру
|
|
170
|
+
*/
|
|
171
|
+
private matchesPathFilter;
|
|
172
|
+
/**
|
|
173
|
+
* Очистить все подписки и disposers
|
|
174
|
+
*/
|
|
175
|
+
dispose(): void;
|
|
176
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FormNode } from '../nodes/form-node';
|
|
2
|
+
import { FormProxy } from '../types/form-proxy';
|
|
3
|
+
/**
|
|
4
|
+
* Создать Proxy для типобезопасного доступа к полям GroupNode
|
|
5
|
+
*
|
|
6
|
+
* Proxy обеспечивает:
|
|
7
|
+
* - Доступ к полям через точечную нотацию (form.email, form.address.city)
|
|
8
|
+
* - Приоритет собственных свойств GroupNode над полями
|
|
9
|
+
* - Цепочку proxy для вложенных GroupNode
|
|
10
|
+
* - Блокировку прямого присваивания полям (только через setValue/patchValue)
|
|
11
|
+
*
|
|
12
|
+
* @param target - GroupNode для которого создаётся proxy
|
|
13
|
+
* @param fields - Map полей формы
|
|
14
|
+
* @returns Типизированный Proxy
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const proxy = buildFormProxy(groupNode, groupNode._fields);
|
|
21
|
+
* proxy.email.setValue('test@example.com');
|
|
22
|
+
* console.log(proxy.email.value.value);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildFormProxy<T>(target: FormNode<T>, fields: Map<keyof T, FormNode<unknown>>): FormProxy<T>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { FormFields } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Интерфейс формы для FormSubmitter
|
|
5
|
+
* Минимальный контракт для работы с любой формой
|
|
6
|
+
*/
|
|
7
|
+
export interface SubmittableForm<T extends FormFields> {
|
|
8
|
+
/** Пометить все поля как touched */
|
|
9
|
+
markAsTouched(): void;
|
|
10
|
+
/** Валидировать форму */
|
|
11
|
+
validate(): Promise<boolean>;
|
|
12
|
+
/** Получить значения формы */
|
|
13
|
+
getValue(): T;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Опции для submit
|
|
17
|
+
*/
|
|
18
|
+
export interface SubmitOptions {
|
|
19
|
+
/** Пропустить валидацию перед submit */
|
|
20
|
+
skipValidation?: boolean;
|
|
21
|
+
/** Пропустить markAsTouched перед submit */
|
|
22
|
+
skipTouch?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Результат submit
|
|
26
|
+
*/
|
|
27
|
+
export interface SubmitResult<R> {
|
|
28
|
+
/** Успешно ли выполнен submit */
|
|
29
|
+
success: boolean;
|
|
30
|
+
/** Результат от onSubmit callback */
|
|
31
|
+
data: R | null;
|
|
32
|
+
/** Ошибка, если submit не удался */
|
|
33
|
+
error?: Error;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* FormSubmitter - управляет процессом отправки формы
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const submitter = new FormSubmitter(form);
|
|
41
|
+
*
|
|
42
|
+
* // Простой submit
|
|
43
|
+
* const result = await submitter.submit(async (values) => {
|
|
44
|
+
* return await api.saveForm(values);
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Проверка состояния
|
|
48
|
+
* if (submitter.submitting.value) {
|
|
49
|
+
* console.log('Форма отправляется...');
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class FormSubmitter<T extends FormFields> {
|
|
54
|
+
private readonly form;
|
|
55
|
+
/** Внутренний сигнал состояния отправки */
|
|
56
|
+
private readonly _submitting;
|
|
57
|
+
/** Публичный read-only сигнал состояния отправки */
|
|
58
|
+
readonly submitting: ReadonlySignal<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* @param form - Форма для отправки
|
|
61
|
+
*/
|
|
62
|
+
constructor(form: SubmittableForm<T>);
|
|
63
|
+
/**
|
|
64
|
+
* Отправить форму
|
|
65
|
+
*
|
|
66
|
+
* Процесс:
|
|
67
|
+
* 1. Помечает все поля как touched (для отображения ошибок)
|
|
68
|
+
* 2. Валидирует форму
|
|
69
|
+
* 3. Если валидация успешна - вызывает onSubmit
|
|
70
|
+
* 4. Управляет состоянием submitting
|
|
71
|
+
*
|
|
72
|
+
* @param onSubmit - Callback для отправки данных
|
|
73
|
+
* @param options - Опции submit
|
|
74
|
+
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const result = await submitter.submit(async (values) => {
|
|
79
|
+
* const response = await fetch('/api/form', {
|
|
80
|
+
* method: 'POST',
|
|
81
|
+
* body: JSON.stringify(values)
|
|
82
|
+
* });
|
|
83
|
+
* return response.json();
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* if (result === null) {
|
|
87
|
+
* console.log('Форма не прошла валидацию');
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
submit<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<R | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Отправить форму с расширенным результатом
|
|
94
|
+
*
|
|
95
|
+
* В отличие от submit(), возвращает объект с информацией об успехе/ошибке
|
|
96
|
+
*
|
|
97
|
+
* @param onSubmit - Callback для отправки данных
|
|
98
|
+
* @param options - Опции submit
|
|
99
|
+
* @returns Объект SubmitResult с данными и статусом
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const result = await submitter.submitWithResult(async (values) => {
|
|
104
|
+
* return await api.saveForm(values);
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* if (result.success) {
|
|
108
|
+
* console.log('Сохранено:', result.data);
|
|
109
|
+
* } else if (result.error) {
|
|
110
|
+
* console.error('Ошибка:', result.error.message);
|
|
111
|
+
* } else {
|
|
112
|
+
* console.log('Валидация не пройдена');
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
submitWithResult<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<SubmitResult<R>>;
|
|
117
|
+
/**
|
|
118
|
+
* Проверить, идет ли отправка формы
|
|
119
|
+
*/
|
|
120
|
+
isSubmitting(): boolean;
|
|
121
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Утилиты для работы с формами
|
|
3
|
+
*
|
|
4
|
+
* Централизованные вспомогательные классы и функции.
|
|
5
|
+
*/
|
|
6
|
+
export { FieldPathNavigator, type PathSegment } from './field-path-navigator';
|
|
7
|
+
export { createFieldPath, extractPath, extractKey, toFieldPath } from './field-path';
|
|
8
|
+
export { SubscriptionManager } from './subscription-manager';
|
|
9
|
+
export { getCurrentValidationRegistry, getCurrentBehaviorRegistry } from './registry-helpers';
|
|
10
|
+
export { RegistryStack } from './registry-stack';
|
|
11
|
+
export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
|
|
12
|
+
export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
13
|
+
export { createForm } from './create-form';
|
|
14
|
+
export { uniqueId } from './unique-id';
|
|
15
|
+
export { safeCallback, runOutsideEffect, safeDebouncedCallback } from './safe-effect';
|
|
16
|
+
export { AbstractRegistry } from './abstract-registry';
|
|
17
|
+
export { FormSubmitter } from './form-submitter';
|
|
18
|
+
export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
|
|
19
|
+
export { FormStatusMachine } from './status-machine';
|
|
20
|
+
export type { StatusEvent } from './status-machine';
|
|
21
|
+
export { FormObserver } from './form-observer';
|
|
22
|
+
export type { FormChangeType, FormChangeEvent, FormChangeCallback, FormObserverOptions, ObservableForm, ObservableFormNode, } from './form-observer';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ValidationRegistry } from '../validation/validation-registry';
|
|
2
|
+
import { BehaviorRegistry } from '../behavior/behavior-registry';
|
|
3
|
+
/**
|
|
4
|
+
* Получить текущий активный ValidationRegistry из context stack
|
|
5
|
+
*
|
|
6
|
+
* Используется внутри validation schema функций (validate, validateAsync, required и т.д.)
|
|
7
|
+
* для регистрации валидаторов в активном контексте GroupNode
|
|
8
|
+
*
|
|
9
|
+
* @returns Текущий ValidationRegistry или заглушка в production
|
|
10
|
+
* @throws Error если нет активного контекста (только в DEV режиме)
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* function required(fieldPath, options) {
|
|
17
|
+
* const registry = getCurrentValidationRegistry();
|
|
18
|
+
* registry.registerSync(path, validator, options);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCurrentValidationRegistry(): ValidationRegistry;
|
|
23
|
+
/**
|
|
24
|
+
* Получить текущий активный BehaviorRegistry из context stack
|
|
25
|
+
*
|
|
26
|
+
* Используется внутри behavior schema функций (copyFrom, enableWhen, computeFrom и т.д.)
|
|
27
|
+
* для регистрации поведений в активном контексте GroupNode
|
|
28
|
+
*
|
|
29
|
+
* @returns Текущий BehaviorRegistry или заглушка в production
|
|
30
|
+
* @throws Error если нет активного контекста (только в DEV режиме)
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* function copyFrom(target, source, options) {
|
|
37
|
+
* const registry = getCurrentBehaviorRegistry();
|
|
38
|
+
* const handler = createCopyBehavior(target, source, options);
|
|
39
|
+
* registry.register(handler, { debounce: options?.debounce });
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function getCurrentBehaviorRegistry(): BehaviorRegistry;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Registry Stack - утилита для управления стеком регистрации
|
|
3
|
+
*
|
|
4
|
+
* Используется ValidationRegistry и BehaviorRegistry для tracking активного контекста.
|
|
5
|
+
* Устраняет дублирование кода между параллельными системами.
|
|
6
|
+
*
|
|
7
|
+
* @template T - Тип элементов в стеке (ValidationRegistry или BehaviorRegistry)
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class ValidationRegistry {
|
|
14
|
+
* private static registryStack = new RegistryStack<ValidationRegistry>();
|
|
15
|
+
*
|
|
16
|
+
* static getCurrent() {
|
|
17
|
+
* return ValidationRegistry.registryStack.getCurrent();
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* beginRegistration() {
|
|
21
|
+
* ValidationRegistry.registryStack.push(this);
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* endRegistration() {
|
|
25
|
+
* ValidationRegistry.registryStack.verify(this, 'ValidationRegistry');
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class RegistryStack<T> {
|
|
31
|
+
private stack;
|
|
32
|
+
/**
|
|
33
|
+
* Добавить элемент в стек
|
|
34
|
+
* @param item - Элемент для добавления
|
|
35
|
+
*/
|
|
36
|
+
push(item: T): void;
|
|
37
|
+
/**
|
|
38
|
+
* Извлечь элемент из стека
|
|
39
|
+
* @returns Извлеченный элемент или undefined если стек пуст
|
|
40
|
+
*/
|
|
41
|
+
pop(): T | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Получить текущий элемент (вершину стека) без извлечения
|
|
44
|
+
* @returns Текущий элемент или null если стек пуст
|
|
45
|
+
*/
|
|
46
|
+
getCurrent(): T | null;
|
|
47
|
+
/**
|
|
48
|
+
* Извлечь элемент из стека с проверкой
|
|
49
|
+
* Выводит предупреждение в DEV режиме если извлеченный элемент не совпадает с ожидаемым
|
|
50
|
+
*
|
|
51
|
+
* @param expected - Ожидаемый элемент
|
|
52
|
+
* @param name - Имя реестра для отладки (например, 'ValidationRegistry')
|
|
53
|
+
*/
|
|
54
|
+
verify(expected: T, name: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Получить длину стека
|
|
57
|
+
* @returns Количество элементов в стеке
|
|
58
|
+
*/
|
|
59
|
+
get length(): number;
|
|
60
|
+
/**
|
|
61
|
+
* Проверить, пуст ли стек
|
|
62
|
+
* @returns true если стек пуст
|
|
63
|
+
*/
|
|
64
|
+
isEmpty(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Очистить стек
|
|
67
|
+
*/
|
|
68
|
+
clear(): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Утилиты для безопасной работы с сигналами в effect контексте
|
|
3
|
+
*
|
|
4
|
+
* Эти утилиты решают проблему "Cycle detected" в @preact/signals-core,
|
|
5
|
+
* которая возникает при модификации сигналов внутри effect.
|
|
6
|
+
*
|
|
7
|
+
* @group Utils
|
|
8
|
+
* @module utils/safe-effect
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Создает callback, который выполняется вне контекста effect
|
|
12
|
+
*
|
|
13
|
+
* Использует queueMicrotask для отложенного выполнения,
|
|
14
|
+
* что позволяет модифицировать сигналы без вызова "Cycle detected"
|
|
15
|
+
*
|
|
16
|
+
* @param callback - Функция для выполнения
|
|
17
|
+
* @returns Обёрнутая функция, безопасная для вызова внутри effect
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Вместо:
|
|
22
|
+
* effect(() => {
|
|
23
|
+
* queueMicrotask(() => {
|
|
24
|
+
* callback(value, context);
|
|
25
|
+
* });
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Используем:
|
|
29
|
+
* effect(() => {
|
|
30
|
+
* safeCallback(callback)(value, context);
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function safeCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void | Promise<void>): (...args: TArgs) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Выполняет функцию вне контекста effect
|
|
37
|
+
*
|
|
38
|
+
* Более простой вариант для одиночного вызова
|
|
39
|
+
*
|
|
40
|
+
* @param fn - Функция для выполнения
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* effect(() => {
|
|
45
|
+
* const value = signal.value;
|
|
46
|
+
* runOutsideEffect(() => {
|
|
47
|
+
* otherSignal.value = transform(value);
|
|
48
|
+
* });
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function runOutsideEffect(fn: () => void | Promise<void>): void;
|
|
53
|
+
/**
|
|
54
|
+
* Создает версию callback с поддержкой debounce, безопасную для effect
|
|
55
|
+
*
|
|
56
|
+
* Комбинирует debounce логику с выходом из effect контекста
|
|
57
|
+
*
|
|
58
|
+
* @param callback - Функция для выполнения
|
|
59
|
+
* @param withDebounce - Функция debounce обёртки из BehaviorRegistry
|
|
60
|
+
* @returns Обёрнутая функция
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* return effect(() => {
|
|
65
|
+
* const value = node.value.value;
|
|
66
|
+
* safeDebouncedCallback(
|
|
67
|
+
* () => callback(value, context),
|
|
68
|
+
* withDebounce
|
|
69
|
+
* )();
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function safeDebouncedCallback(callback: () => void | Promise<void>, withDebounce: (fn: () => void) => void): () => void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { FieldStatus } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* События для State Machine
|
|
5
|
+
*/
|
|
6
|
+
export type StatusEvent = {
|
|
7
|
+
type: 'START_VALIDATION';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'VALIDATION_SUCCESS';
|
|
10
|
+
} | {
|
|
11
|
+
type: 'VALIDATION_FAILURE';
|
|
12
|
+
} | {
|
|
13
|
+
type: 'DISABLE';
|
|
14
|
+
} | {
|
|
15
|
+
type: 'ENABLE';
|
|
16
|
+
hasErrors?: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'SET_ERRORS';
|
|
19
|
+
hasErrors: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* FormStatusMachine - управляет состоянием поля формы
|
|
23
|
+
*
|
|
24
|
+
* Предоставляет:
|
|
25
|
+
* - Единый источник истины для статуса
|
|
26
|
+
* - Computed signals для derived состояний (valid, invalid, pending, disabled)
|
|
27
|
+
* - Валидацию переходов между состояниями
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const statusMachine = new FormStatusMachine('valid');
|
|
32
|
+
*
|
|
33
|
+
* // Начало валидации
|
|
34
|
+
* statusMachine.startValidation();
|
|
35
|
+
* console.log(statusMachine.pending.value); // true
|
|
36
|
+
*
|
|
37
|
+
* // Завершение валидации с ошибками
|
|
38
|
+
* statusMachine.completeValidation(true);
|
|
39
|
+
* console.log(statusMachine.invalid.value); // true
|
|
40
|
+
*
|
|
41
|
+
* // Отключение поля
|
|
42
|
+
* statusMachine.disable();
|
|
43
|
+
* console.log(statusMachine.disabled.value); // true
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class FormStatusMachine {
|
|
47
|
+
/** Внутренний сигнал статуса */
|
|
48
|
+
private readonly _status;
|
|
49
|
+
/** Публичный read-only сигнал статуса */
|
|
50
|
+
readonly status: ReadonlySignal<FieldStatus>;
|
|
51
|
+
/** Поле валидно */
|
|
52
|
+
readonly valid: ReadonlySignal<boolean>;
|
|
53
|
+
/** Поле невалидно */
|
|
54
|
+
readonly invalid: ReadonlySignal<boolean>;
|
|
55
|
+
/** Идет валидация */
|
|
56
|
+
readonly pending: ReadonlySignal<boolean>;
|
|
57
|
+
/** Поле отключено */
|
|
58
|
+
readonly disabled: ReadonlySignal<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* @param initial - Начальный статус (по умолчанию 'valid')
|
|
61
|
+
*/
|
|
62
|
+
constructor(initial?: FieldStatus);
|
|
63
|
+
/**
|
|
64
|
+
* Начать валидацию
|
|
65
|
+
*
|
|
66
|
+
* Переводит статус в 'pending' если поле не отключено
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* statusMachine.startValidation();
|
|
71
|
+
* // status: 'pending'
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
startValidation(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Завершить валидацию
|
|
77
|
+
*
|
|
78
|
+
* @param hasErrors - Есть ли ошибки валидации
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // Валидация успешна
|
|
83
|
+
* statusMachine.completeValidation(false);
|
|
84
|
+
* // status: 'valid'
|
|
85
|
+
*
|
|
86
|
+
* // Есть ошибки
|
|
87
|
+
* statusMachine.completeValidation(true);
|
|
88
|
+
* // status: 'invalid'
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
completeValidation(hasErrors: boolean): void;
|
|
92
|
+
/**
|
|
93
|
+
* Установить ошибки напрямую (без перехода через pending)
|
|
94
|
+
*
|
|
95
|
+
* Используется для синхронной валидации или установки ошибок извне
|
|
96
|
+
*
|
|
97
|
+
* @param hasErrors - Есть ли ошибки
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* statusMachine.setErrors(true);
|
|
102
|
+
* // status: 'invalid'
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
setErrors(hasErrors: boolean): void;
|
|
106
|
+
/**
|
|
107
|
+
* Отключить поле
|
|
108
|
+
*
|
|
109
|
+
* Переводит статус в 'disabled'
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* statusMachine.disable();
|
|
114
|
+
* // status: 'disabled'
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
disable(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Включить поле
|
|
120
|
+
*
|
|
121
|
+
* @param hasErrors - Есть ли ошибки (определяет valid/invalid)
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* statusMachine.enable(false);
|
|
126
|
+
* // status: 'valid'
|
|
127
|
+
*
|
|
128
|
+
* statusMachine.enable(true);
|
|
129
|
+
* // status: 'invalid'
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
enable(hasErrors?: boolean): void;
|
|
133
|
+
/**
|
|
134
|
+
* Обработать событие (альтернативный API)
|
|
135
|
+
*
|
|
136
|
+
* @param event - Событие для обработки
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* statusMachine.dispatch({ type: 'START_VALIDATION' });
|
|
141
|
+
* statusMachine.dispatch({ type: 'VALIDATION_FAILURE' });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
dispatch(event: StatusEvent): void;
|
|
145
|
+
/**
|
|
146
|
+
* Получить текущий статус
|
|
147
|
+
*/
|
|
148
|
+
getStatus(): FieldStatus;
|
|
149
|
+
/**
|
|
150
|
+
* Проверить, можно ли начать валидацию
|
|
151
|
+
*/
|
|
152
|
+
canValidate(): boolean;
|
|
153
|
+
}
|