@reformer/core 9.0.0 → 9.1.0
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/README.md +52 -57
- package/dist/behaviors-node-BMOI2JJF.js +26 -0
- package/dist/behaviors-value-lCpqicfa.js +148 -0
- package/dist/behaviors.d.ts +2 -133
- package/dist/behaviors.js +148 -147
- package/dist/{core/utils → form}/aggregate-signals.d.ts +2 -2
- package/dist/form/behaviors-node.d.ts +55 -0
- package/dist/form/behaviors.d.ts +133 -0
- package/dist/{core/utils → form}/create-form.d.ts +5 -4
- package/dist/{core/utils → form}/error-handler.d.ts +1 -1
- package/dist/{core → form}/factories/node-factory.d.ts +1 -1
- package/dist/{core/utils → form}/form-proxy-builder.d.ts +2 -2
- package/dist/{core/utils → form}/form-submitter.d.ts +6 -1
- package/dist/{hooks → form/hooks}/types.d.ts +31 -1
- package/dist/{hooks → form/hooks}/useArrayLength.d.ts +1 -1
- package/dist/{hooks → form/hooks}/useFormControl.d.ts +3 -3
- package/dist/{hooks → form/hooks}/useFormControlValue.d.ts +2 -2
- package/dist/{hooks → form/hooks}/useSignalSubscription.d.ts +8 -1
- package/dist/form/index.d.ts +36 -0
- package/dist/{core → form}/nodes/array-node.d.ts +1 -1
- package/dist/{core → form}/nodes/field-node.d.ts +1 -1
- package/dist/{core → form}/nodes/form-node.d.ts +1 -1
- package/dist/{core → form}/nodes/group-node.d.ts +34 -4
- package/dist/{core → form}/nodes/model-array-node.d.ts +10 -3
- package/dist/{core/utils → form}/signal-node-registry.d.ts +1 -1
- package/dist/{core/utils → form}/status-machine.d.ts +1 -1
- package/dist/{core/utils → form}/type-guards.d.ts +5 -5
- package/dist/{core/types/index.d.ts → form/types/contracts.d.ts} +11 -90
- package/dist/{core → form}/types/deep-schema.d.ts +23 -1
- package/dist/{core → form}/types/form-proxy.d.ts +26 -1
- package/dist/form/types/index.d.ts +91 -0
- package/dist/{core → form}/types/schema-node.d.ts +1 -1
- package/dist/{core → form}/types/validation-schema.d.ts +19 -9
- package/dist/{core/model/validate-model.d.ts → form/validate-model-core.d.ts} +25 -25
- package/dist/form/validate-model.d.ts +27 -0
- package/dist/{core → form}/validation/validators/required.d.ts +2 -1
- package/dist/form/validators.d.ts +6 -0
- package/dist/form-model-C8Fi83k4.js +439 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.js +666 -1011
- package/dist/{core/model/behaviors.d.ts → state/behaviors-value.d.ts} +0 -38
- package/dist/{core/utils → state}/derived-registry.d.ts +29 -0
- package/dist/state/form-model.d.ts +26 -0
- package/dist/state/index.d.ts +31 -0
- package/dist/state/safe-effect.d.ts +93 -0
- package/dist/{core/model → state}/types.d.ts +14 -11
- package/dist/state.d.ts +2 -0
- package/dist/state.js +19 -0
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/pattern.js +7 -6
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +5 -9
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators.d.ts +6 -6
- package/llms.txt +307 -166
- package/package.json +5 -1
- package/dist/behaviors-O9a1Djj9.js +0 -147
- package/dist/core/model/form-model.d.ts +0 -18
- package/dist/core/model/index.d.ts +0 -12
- package/dist/core/utils/index.d.ts +0 -18
- package/dist/core/utils/safe-effect.d.ts +0 -73
- /package/dist/{core → form}/factories/index.d.ts +0 -0
- /package/dist/{core/utils → form}/unique-id.d.ts +0 -0
- /package/dist/{core → form}/validation/index.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/date-utils.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/email.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/future-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/index.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/integer.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/is-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/is-number.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-age.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-length.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-age.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-length.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/multiple-of.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/non-negative.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/non-zero.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/past-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/pattern.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/phone.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/url.d.ts +0 -0
- /package/dist/{core/utils → state}/subscription-manager.d.ts +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FormValue, ValidationError } from './index';
|
|
2
|
-
import { FormModel } from '
|
|
2
|
+
import { FormModel } from '../../state/types';
|
|
3
3
|
/**
|
|
4
4
|
* Чистый синхронный валидатор поля.
|
|
5
5
|
*
|
|
@@ -35,27 +35,37 @@ import { FormModel } from '../model/types';
|
|
|
35
35
|
export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
|
|
36
36
|
/**
|
|
37
37
|
* Чистый асинхронный валидатор поля. Та же тройка аргументов `(value, scope, root)`, что у
|
|
38
|
-
* {@link Validator}, но возвращает `Promise`.
|
|
39
|
-
*
|
|
38
|
+
* {@link Validator}, но возвращает `Promise`.
|
|
39
|
+
*
|
|
40
|
+
* @deprecated Осиротевший остаток удалённого оператора `validateAsync` (Ф7). Рантаймом не
|
|
41
|
+
* потребляется — живой async-путь узла поля использует `AsyncValidatorFn` `(value, { signal })`.
|
|
42
|
+
* Экспортируется только ради обратной совместимости.
|
|
40
43
|
*/
|
|
41
44
|
export type AsyncValidator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => Promise<ValidationError | null>;
|
|
42
45
|
/**
|
|
43
|
-
*
|
|
46
|
+
* Функция-предикат `(value) => boolean`.
|
|
47
|
+
*
|
|
48
|
+
* @deprecated Осиротевший остаток удалённого оператора `applyWhen` (Ф7). Рантаймом не
|
|
49
|
+
* потребляется; экспортируется только ради обратной совместимости.
|
|
44
50
|
*/
|
|
45
51
|
export type ConditionFn<T> = (value: T) => boolean;
|
|
46
52
|
/**
|
|
47
|
-
* Опции
|
|
53
|
+
* Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)
|
|
54
|
+
* аргументом в фабрику и попадают в возвращаемую {@link ValidationError}.
|
|
48
55
|
*/
|
|
49
56
|
export interface ValidateOptions {
|
|
50
|
-
/**
|
|
57
|
+
/** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
|
|
58
|
+
* резолвится из `code` (см. резолвер сообщений в `@reformer/cdk`). */
|
|
51
59
|
message?: string;
|
|
52
|
-
/** Параметры ошибки */
|
|
60
|
+
/** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
|
|
53
61
|
params?: Record<string, FormValue>;
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
56
|
-
*
|
|
64
|
+
* @deprecated Осиротевший остаток удалённого оператора `validateAsync` (Ф7): опция `debounce`
|
|
65
|
+
* подключалась тем оператором, которого больше нет. Рантаймом не потребляется; экспортируется
|
|
66
|
+
* только ради обратной совместимости.
|
|
57
67
|
*/
|
|
58
68
|
export interface ValidateAsyncOptions extends ValidateOptions {
|
|
59
|
-
/** Задержка перед выполнением валидации (в мс) */
|
|
69
|
+
/** Задержка перед выполнением валидации (в мс). */
|
|
60
70
|
debounce?: number;
|
|
61
71
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ValidationError } from '
|
|
2
|
-
import { FormSchemaNode } from '
|
|
3
|
-
import { FormModel } from '
|
|
1
|
+
import { ValidationError } from './types/contracts';
|
|
2
|
+
import { FormSchemaNode } from './types/schema-node';
|
|
3
|
+
import { FormModel, PathAwareSignal } from '../state/types';
|
|
4
4
|
/**
|
|
5
5
|
* Валидатор слоя **данных**. `value` — значение поля; `model` — ближайший scope (под-модель элемента
|
|
6
6
|
* массива или корень); `root` — корневая модель.
|
|
@@ -21,6 +21,26 @@ export interface ModelValidationResult {
|
|
|
21
21
|
/** Ключ — путь поля (`'coBorrowers.0.relationship'`). Только поля с ошибками. */
|
|
22
22
|
errors: Record<string, ValidationError[]>;
|
|
23
23
|
}
|
|
24
|
+
export interface FieldTask {
|
|
25
|
+
signal: PathAwareSignal<unknown>;
|
|
26
|
+
validators: ModelValidator[];
|
|
27
|
+
scope: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** Результат обхода схемы: что валидировать и что очистить. */
|
|
30
|
+
export interface CollectResult {
|
|
31
|
+
/** Активные поля с валидаторами (прогоняются). */
|
|
32
|
+
tasks: FieldTask[];
|
|
33
|
+
/** Сигналы полей в ВЫКЛЮЧЕННЫХ ветках — их ошибки нужно очистить (`setErrors([])`). */
|
|
34
|
+
clearSignals: PathAwareSignal<unknown>[];
|
|
35
|
+
}
|
|
36
|
+
/** Один обход схемы → задачи (активные) + сигналы на очистку (выключенные ветки). */
|
|
37
|
+
export declare function collect(model: unknown, schema: unknown): CollectResult;
|
|
38
|
+
/**
|
|
39
|
+
* Есть ли среди собранных ошибок блокирующие. Ошибка с `severity: 'warning'` показывается,
|
|
40
|
+
* но НЕ блокирует submit (см. {@link ValidationError.severity} — 'warning' allows submission),
|
|
41
|
+
* поэтому `valid` выводится из наличия блокирующих ошибок, а не из общего количества ошибок.
|
|
42
|
+
*/
|
|
43
|
+
export declare const hasBlockingErrors: (errors: Record<string, ValidationError[]>) => boolean;
|
|
24
44
|
/**
|
|
25
45
|
* Синхронная headless-валидация данных. Асинхронные валидаторы пропускаются.
|
|
26
46
|
*
|
|
@@ -48,25 +68,5 @@ export declare function validateModelSync<T>(model: FormModel<T>, schema: FormSc
|
|
|
48
68
|
* ```
|
|
49
69
|
*/
|
|
50
70
|
export declare function validateModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
* (через реестр сигнал→нода): `node.setErrors(errors[path] ?? [])` — заодно очищает прошедшие поля
|
|
54
|
-
* И поля выключенных веток (`{ when, children }` с ложным условием). Дерево обходится ОДИН раз.
|
|
55
|
-
*
|
|
56
|
-
* @group Model
|
|
57
|
-
* @param model - Модель данных ({@link FormModel}) — источник значений полей.
|
|
58
|
-
* @param schema - Единая схема формы (та же, что передавалась в `createForm`).
|
|
59
|
-
* @returns {@link ModelValidationResult} — `valid` + ошибки по пути поля; побочный эффект — ошибки
|
|
60
|
-
* разведены по нодам формы.
|
|
61
|
-
* @remarks Поля, не материализованные в форме (элементы массива — строятся per-item), не роутятся
|
|
62
|
-
* в родительскую форму; их ошибки доступны в возвращаемом результате по пути.
|
|
63
|
-
*
|
|
64
|
-
* @example Валидация перед submit (ошибки показываются в UI автоматически)
|
|
65
|
-
* ```typescript
|
|
66
|
-
* const form = createForm({ model, schema });
|
|
67
|
-
* form.touchAll();
|
|
68
|
-
* const res = await validateFormModel(model, schema);
|
|
69
|
-
* if (res.valid) await api.save(model.get());
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
export declare function validateFormModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
|
|
71
|
+
/** Прогон задач (sync + async): возвращает ошибки по пути. */
|
|
72
|
+
export declare function runTasks(tasks: FieldTask[], model: unknown): Promise<Record<string, ValidationError[]>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FormSchemaNode } from './types/schema-node';
|
|
2
|
+
import { FormModel } from '../state/types';
|
|
3
|
+
import { ModelValidationResult } from './validate-model-core';
|
|
4
|
+
export { validateModel, validateModelSync } from './validate-model-core';
|
|
5
|
+
export type { ModelValidator, ModelValidationResult } from './validate-model-core';
|
|
6
|
+
/**
|
|
7
|
+
* In-form валидация: прогоняет {@link validateModel} и роутит ошибки в ноды формы
|
|
8
|
+
* (через реестр сигнал→нода): `node.setErrors(errors[path] ?? [])` — заодно очищает прошедшие поля
|
|
9
|
+
* И поля выключенных веток (`{ when, children }` с ложным условием). Дерево обходится ОДИН раз.
|
|
10
|
+
*
|
|
11
|
+
* @group Model
|
|
12
|
+
* @param model - Модель данных ({@link FormModel}) — источник значений полей.
|
|
13
|
+
* @param schema - Единая схема формы (та же, что передавалась в `createForm`).
|
|
14
|
+
* @returns {@link ModelValidationResult} — `valid` + ошибки по пути поля; побочный эффект — ошибки
|
|
15
|
+
* разведены по нодам формы.
|
|
16
|
+
* @remarks Поля, не материализованные в форме (элементы массива — строятся per-item), не роутятся
|
|
17
|
+
* в родительскую форму; их ошибки доступны в возвращаемом результате по пути.
|
|
18
|
+
*
|
|
19
|
+
* @example Валидация перед submit (ошибки показываются в UI автоматически)
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const form = createForm({ model, schema });
|
|
22
|
+
* form.touchAll();
|
|
23
|
+
* const res = await validateFormModel(model, schema);
|
|
24
|
+
* if (res.valid) await api.save(model.get());
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateFormModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
|
|
@@ -4,7 +4,8 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
4
4
|
*
|
|
5
5
|
* Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
|
|
6
6
|
*
|
|
7
|
-
* Пустыми считаются: `null`, `undefined`, `''` (пустая строка)
|
|
7
|
+
* Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
|
|
8
|
+
* обязательный multi-select / FormArray без выбранных элементов).
|
|
8
9
|
* Для boolean полей требуется значение `true`.
|
|
9
10
|
*
|
|
10
11
|
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { signal as v } from "@preact/signals-core";
|
|
2
|
+
import { i as w } from "./behaviors-value-lCpqicfa.js";
|
|
3
|
+
class P {
|
|
4
|
+
/**
|
|
5
|
+
* Хранилище подписок
|
|
6
|
+
* Ключ: уникальный идентификатор подписки
|
|
7
|
+
* Значение: функция отписки (dispose)
|
|
8
|
+
*/
|
|
9
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Добавляет подписку
|
|
12
|
+
*
|
|
13
|
+
* Если подписка с таким ключом уже существует, отписывается от неё
|
|
14
|
+
* и заменяет новой. Это предотвращает утечки памяти при повторной
|
|
15
|
+
* регистрации подписки с тем же ключом.
|
|
16
|
+
*
|
|
17
|
+
* @param key Уникальный ключ подписки
|
|
18
|
+
* @param dispose Функция отписки (обычно возвращаемая из effect())
|
|
19
|
+
* @returns Функция для отписки от этой конкретной подписки
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const manager = new SubscriptionManager();
|
|
24
|
+
*
|
|
25
|
+
* // Добавление подписки
|
|
26
|
+
* const unsubscribe = manager.add('mySubscription', () => {
|
|
27
|
+
* console.log('Disposing subscription');
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Отписка через возвращаемую функцию
|
|
31
|
+
* unsubscribe();
|
|
32
|
+
*
|
|
33
|
+
* // Или через manager.remove()
|
|
34
|
+
* manager.add('anotherSub', disposeFn);
|
|
35
|
+
* manager.remove('anotherSub');
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
add(t, e) {
|
|
39
|
+
return this.subscriptions.has(t) && (process.env.NODE_ENV !== "production" && console.warn(`[SubscriptionManager] Subscription "${t}" already exists, replacing`), this.subscriptions.get(t)?.()), this.subscriptions.set(t, e), () => this.remove(t);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Удаляет подписку по ключу
|
|
43
|
+
*
|
|
44
|
+
* Вызывает функцию отписки и удаляет подписку из хранилища.
|
|
45
|
+
* Если подписка с таким ключом не найдена, ничего не делает.
|
|
46
|
+
*
|
|
47
|
+
* @param key Ключ подписки
|
|
48
|
+
* @returns true, если подписка была удалена, false если не найдена
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const manager = new SubscriptionManager();
|
|
53
|
+
* manager.add('mySub', disposeFn);
|
|
54
|
+
*
|
|
55
|
+
* // Удаление подписки
|
|
56
|
+
* const removed = manager.remove('mySub'); // true
|
|
57
|
+
*
|
|
58
|
+
* // Попытка удалить несуществующую подписку
|
|
59
|
+
* const removed2 = manager.remove('nonExistent'); // false
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
remove(t) {
|
|
63
|
+
const e = this.subscriptions.get(t);
|
|
64
|
+
return e ? (e(), this.subscriptions.delete(t), !0) : !1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Очищает все подписки
|
|
68
|
+
*
|
|
69
|
+
* Вызывает функции отписки для всех активных подписок
|
|
70
|
+
* и очищает хранилище. Обычно используется при dispose узла формы.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* class FieldNode {
|
|
75
|
+
* private subscriptions = new SubscriptionManager();
|
|
76
|
+
*
|
|
77
|
+
* dispose() {
|
|
78
|
+
* // Отписываемся от всех effect
|
|
79
|
+
* this.subscriptions.clear();
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
clear() {
|
|
85
|
+
this.subscriptions.forEach((t) => {
|
|
86
|
+
t();
|
|
87
|
+
}), this.subscriptions.clear();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Возвращает количество активных подписок
|
|
91
|
+
*
|
|
92
|
+
* Полезно для отладки утечек памяти. Если количество подписок
|
|
93
|
+
* растет без ограничений, это может указывать на то, что
|
|
94
|
+
* компоненты не отписываются должным образом.
|
|
95
|
+
*
|
|
96
|
+
* @returns Количество активных подписок
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const manager = new SubscriptionManager();
|
|
101
|
+
* console.log(manager.size()); // 0
|
|
102
|
+
*
|
|
103
|
+
* manager.add('sub1', disposeFn1);
|
|
104
|
+
* manager.add('sub2', disposeFn2);
|
|
105
|
+
* console.log(manager.size()); // 2
|
|
106
|
+
*
|
|
107
|
+
* manager.clear();
|
|
108
|
+
* console.log(manager.size()); // 0
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
size() {
|
|
112
|
+
return this.subscriptions.size;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Проверяет, есть ли подписка с данным ключом
|
|
116
|
+
*
|
|
117
|
+
* @param key Ключ подписки
|
|
118
|
+
* @returns true, если подписка существует
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const manager = new SubscriptionManager();
|
|
123
|
+
* manager.add('mySub', disposeFn);
|
|
124
|
+
*
|
|
125
|
+
* console.log(manager.has('mySub')); // true
|
|
126
|
+
* console.log(manager.has('nonExistent')); // false
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
has(t) {
|
|
130
|
+
return this.subscriptions.has(t);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Возвращает список всех ключей активных подписок
|
|
134
|
+
*
|
|
135
|
+
* Полезно для отладки: можно увидеть, какие подписки активны.
|
|
136
|
+
*
|
|
137
|
+
* @returns Массив ключей всех активных подписок
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const manager = new SubscriptionManager();
|
|
142
|
+
* manager.add('watch-value', disposeFn1);
|
|
143
|
+
* manager.add('watch-errors', disposeFn2);
|
|
144
|
+
*
|
|
145
|
+
* console.log(manager.getKeys()); // ['watch-value', 'watch-errors']
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
getKeys() {
|
|
149
|
+
return Array.from(this.subscriptions.keys());
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Отписывается от всех подписок (алиас для clear())
|
|
153
|
+
*
|
|
154
|
+
* Используется при dispose() узла формы для совместимости с ожидаемым API.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* class FieldNode {
|
|
159
|
+
* private subscriptions = new SubscriptionManager();
|
|
160
|
+
*
|
|
161
|
+
* dispose() {
|
|
162
|
+
* this.subscriptions.dispose();
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
dispose() {
|
|
168
|
+
this.clear();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const u = (i) => !(i === null || typeof i != "object" || Array.isArray(i) || i instanceof Date || typeof Blob < "u" && i instanceof Blob || typeof File < "u" && i instanceof File), a = (i, t) => i === "" ? String(t) : `${i}.${t}`, p = (i) => /^\d+$/.test(i), o = (i) => {
|
|
172
|
+
if (Array.isArray(i)) return i.map(o);
|
|
173
|
+
if (u(i)) {
|
|
174
|
+
const t = {};
|
|
175
|
+
for (const e of Object.keys(i)) t[e] = o(i[e]);
|
|
176
|
+
return t;
|
|
177
|
+
}
|
|
178
|
+
return i;
|
|
179
|
+
}, f = (i, t) => {
|
|
180
|
+
if (i === t) return !0;
|
|
181
|
+
if (Array.isArray(i) && Array.isArray(t))
|
|
182
|
+
return i.length !== t.length ? !1 : i.every((e, s) => f(e, t[s]));
|
|
183
|
+
if (u(i) && u(t)) {
|
|
184
|
+
const e = Object.keys(i), s = Object.keys(t);
|
|
185
|
+
return e.length !== s.length ? !1 : e.every((r) => f(i[r], t[r]));
|
|
186
|
+
}
|
|
187
|
+
return !1;
|
|
188
|
+
};
|
|
189
|
+
class _ {
|
|
190
|
+
constructor(t, e) {
|
|
191
|
+
this.path = e;
|
|
192
|
+
const s = v(t);
|
|
193
|
+
s.__path = e, this.signal = s, this.initial = o(t);
|
|
194
|
+
}
|
|
195
|
+
kind = "leaf";
|
|
196
|
+
signal;
|
|
197
|
+
initial;
|
|
198
|
+
rebase(t) {
|
|
199
|
+
this.path = t, this.signal.__path = t;
|
|
200
|
+
}
|
|
201
|
+
/** Реактивное чтение (внутри effect/computed создаёт зависимость). */
|
|
202
|
+
read() {
|
|
203
|
+
return this.signal.value;
|
|
204
|
+
}
|
|
205
|
+
/** Нереактивный снимок. */
|
|
206
|
+
peek() {
|
|
207
|
+
return this.signal.peek();
|
|
208
|
+
}
|
|
209
|
+
set(t) {
|
|
210
|
+
this.signal.value = t;
|
|
211
|
+
}
|
|
212
|
+
resetToInitial() {
|
|
213
|
+
this.signal.value = o(this.initial);
|
|
214
|
+
}
|
|
215
|
+
captureInitial() {
|
|
216
|
+
this.initial = o(this.signal.peek());
|
|
217
|
+
}
|
|
218
|
+
dirty() {
|
|
219
|
+
return !f(this.signal.peek(), this.initial);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
class b {
|
|
223
|
+
constructor(t, e) {
|
|
224
|
+
this.path = e;
|
|
225
|
+
for (const s of Object.keys(t))
|
|
226
|
+
this.children.set(s, c(t[s], a(e, s)));
|
|
227
|
+
}
|
|
228
|
+
kind = "group";
|
|
229
|
+
children = /* @__PURE__ */ new Map();
|
|
230
|
+
rebase(t) {
|
|
231
|
+
this.path = t;
|
|
232
|
+
for (const [e, s] of this.children) s.rebase(a(t, e));
|
|
233
|
+
}
|
|
234
|
+
peek() {
|
|
235
|
+
const t = {};
|
|
236
|
+
for (const [e, s] of this.children) t[e] = s.peek();
|
|
237
|
+
return t;
|
|
238
|
+
}
|
|
239
|
+
set(t) {
|
|
240
|
+
if (t == null || typeof t != "object") return;
|
|
241
|
+
const e = t;
|
|
242
|
+
for (const [s, r] of this.children)
|
|
243
|
+
s in e && (r.kind === "leaf" && w(r.signal) || r.set(e[s]));
|
|
244
|
+
}
|
|
245
|
+
resetToInitial() {
|
|
246
|
+
for (const t of this.children.values()) t.resetToInitial();
|
|
247
|
+
}
|
|
248
|
+
captureInitial() {
|
|
249
|
+
for (const t of this.children.values()) t.captureInitial();
|
|
250
|
+
}
|
|
251
|
+
dirty() {
|
|
252
|
+
for (const t of this.children.values()) if (t.dirty()) return !0;
|
|
253
|
+
return !1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
class k {
|
|
257
|
+
constructor(t, e) {
|
|
258
|
+
this.path = e, this.items = v(t.map((s, r) => c(s, a(e, r)))), this.initial = o(t);
|
|
259
|
+
}
|
|
260
|
+
kind = "array";
|
|
261
|
+
items;
|
|
262
|
+
initial;
|
|
263
|
+
rebase(t) {
|
|
264
|
+
this.path = t, this.items.peek().forEach((e, s) => e.rebase(a(t, s)));
|
|
265
|
+
}
|
|
266
|
+
reindex() {
|
|
267
|
+
this.items.peek().forEach((t, e) => t.rebase(a(this.path, e)));
|
|
268
|
+
}
|
|
269
|
+
push(t) {
|
|
270
|
+
const e = this.items.peek();
|
|
271
|
+
this.items.value = [...e, c(t, a(this.path, e.length))];
|
|
272
|
+
}
|
|
273
|
+
insertAt(t, e) {
|
|
274
|
+
const s = [...this.items.peek()];
|
|
275
|
+
s.splice(t, 0, c(e, a(this.path, t))), this.items.value = s, this.reindex();
|
|
276
|
+
}
|
|
277
|
+
removeAt(t) {
|
|
278
|
+
const e = [...this.items.peek()];
|
|
279
|
+
e.splice(t, 1), this.items.value = e, this.reindex();
|
|
280
|
+
}
|
|
281
|
+
move(t, e) {
|
|
282
|
+
const s = [...this.items.peek()], [r] = s.splice(t, 1);
|
|
283
|
+
r && s.splice(e, 0, r), this.items.value = s, this.reindex();
|
|
284
|
+
}
|
|
285
|
+
swap(t, e) {
|
|
286
|
+
const s = [...this.items.peek()];
|
|
287
|
+
t < 0 || e < 0 || t >= s.length || e >= s.length || t === e || ([s[t], s[e]] = [s[e], s[t]], this.items.value = s, this.reindex());
|
|
288
|
+
}
|
|
289
|
+
clear() {
|
|
290
|
+
this.items.value = [];
|
|
291
|
+
}
|
|
292
|
+
peek() {
|
|
293
|
+
return this.items.peek().map((t) => t.peek());
|
|
294
|
+
}
|
|
295
|
+
set(t) {
|
|
296
|
+
const e = Array.isArray(t) ? t : [];
|
|
297
|
+
this.items.value = e.map((s, r) => c(s, a(this.path, r)));
|
|
298
|
+
}
|
|
299
|
+
resetToInitial() {
|
|
300
|
+
this.set(o(this.initial));
|
|
301
|
+
}
|
|
302
|
+
captureInitial() {
|
|
303
|
+
this.initial = o(this.peek());
|
|
304
|
+
}
|
|
305
|
+
dirty() {
|
|
306
|
+
return !f(this.peek(), this.initial);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function c(i, t) {
|
|
310
|
+
return Array.isArray(i) ? new k(i, t) : u(i) ? new b(i, t) : new _(i, t);
|
|
311
|
+
}
|
|
312
|
+
function l(i) {
|
|
313
|
+
if (i)
|
|
314
|
+
return i.kind === "group" ? A(i) : i.kind === "array" ? x(i) : i.read();
|
|
315
|
+
}
|
|
316
|
+
function x(i) {
|
|
317
|
+
const t = {
|
|
318
|
+
get length() {
|
|
319
|
+
return i.items.value.length;
|
|
320
|
+
},
|
|
321
|
+
push: (e) => i.push(e),
|
|
322
|
+
insertAt: (e, s) => i.insertAt(e, s),
|
|
323
|
+
removeAt: (e) => i.removeAt(e),
|
|
324
|
+
move: (e, s) => i.move(e, s),
|
|
325
|
+
swap: (e, s) => i.swap(e, s),
|
|
326
|
+
clear: () => i.clear(),
|
|
327
|
+
at: (e) => l(i.items.value[e]),
|
|
328
|
+
map: (e) => i.items.value.map((s, r) => e(l(s), r)),
|
|
329
|
+
forEach: (e) => i.items.value.forEach((s, r) => e(l(s), r)),
|
|
330
|
+
toArray: () => i.peek(),
|
|
331
|
+
[Symbol.iterator]: function* () {
|
|
332
|
+
const e = i.items.value;
|
|
333
|
+
for (let s = 0; s < e.length; s++) yield l(e[s]);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
return new Proxy(t, {
|
|
337
|
+
get: (e, s, r) => s === "__path" ? i.path : typeof s == "string" && p(s) ? l(i.items.value[Number(s)]) : Reflect.get(e, s, r),
|
|
338
|
+
has: (e, s) => typeof s == "string" && p(s) ? Number(s) < i.items.value.length : Reflect.has(e, s)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function d(i) {
|
|
342
|
+
if (i.kind === "leaf") return i.signal;
|
|
343
|
+
if (i.kind === "group")
|
|
344
|
+
return new Proxy(
|
|
345
|
+
{},
|
|
346
|
+
{
|
|
347
|
+
get: (e, s) => {
|
|
348
|
+
if (s === "__path") return i.path;
|
|
349
|
+
if (typeof s != "string") return;
|
|
350
|
+
const r = i.children.get(s);
|
|
351
|
+
return r ? d(r) : void 0;
|
|
352
|
+
},
|
|
353
|
+
has: (e, s) => typeof s == "string" && i.children.has(s),
|
|
354
|
+
ownKeys: () => [...i.children.keys()],
|
|
355
|
+
getOwnPropertyDescriptor: (e, s) => typeof s == "string" && i.children.has(s) ? { enumerable: !0, configurable: !0 } : void 0
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
const t = i;
|
|
359
|
+
return new Proxy(
|
|
360
|
+
{
|
|
361
|
+
get length() {
|
|
362
|
+
return t.items.value.length;
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
get: (e, s, r) => {
|
|
367
|
+
if (s === "__path") return t.path;
|
|
368
|
+
if (typeof s == "string" && p(s)) {
|
|
369
|
+
const n = t.items.value[Number(s)];
|
|
370
|
+
return n ? d(n) : void 0;
|
|
371
|
+
}
|
|
372
|
+
return Reflect.get(e, s, r);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function I(i, t) {
|
|
378
|
+
if (!t) return;
|
|
379
|
+
let e = i;
|
|
380
|
+
for (const s of t.split(".")) {
|
|
381
|
+
if (!e) return;
|
|
382
|
+
if (e.kind === "group") e = e.children.get(s);
|
|
383
|
+
else if (e.kind === "array") e = e.items.peek()[Number(s)];
|
|
384
|
+
else return;
|
|
385
|
+
}
|
|
386
|
+
return e && e.kind === "leaf" ? e.signal : void 0;
|
|
387
|
+
}
|
|
388
|
+
const g = /* @__PURE__ */ new Set([
|
|
389
|
+
"$",
|
|
390
|
+
"get",
|
|
391
|
+
"set",
|
|
392
|
+
"patch",
|
|
393
|
+
"isDirty",
|
|
394
|
+
"reset",
|
|
395
|
+
"captureInitial",
|
|
396
|
+
"signalAt"
|
|
397
|
+
]), y = /* @__PURE__ */ new WeakMap();
|
|
398
|
+
function A(i) {
|
|
399
|
+
const t = y.get(i);
|
|
400
|
+
if (t) return t;
|
|
401
|
+
const e = {
|
|
402
|
+
$: d(i),
|
|
403
|
+
get: () => i.peek(),
|
|
404
|
+
set: (r) => i.set(r),
|
|
405
|
+
patch: (r) => i.set(r),
|
|
406
|
+
isDirty: () => i.dirty(),
|
|
407
|
+
reset: () => i.resetToInitial(),
|
|
408
|
+
captureInitial: () => i.captureInitial(),
|
|
409
|
+
signalAt: (r) => I(i, r)
|
|
410
|
+
}, s = new Proxy(
|
|
411
|
+
{},
|
|
412
|
+
{
|
|
413
|
+
get: (r, n) => {
|
|
414
|
+
if (n === "__path") return i.path;
|
|
415
|
+
if (typeof n != "string") return;
|
|
416
|
+
const h = i.children.get(n);
|
|
417
|
+
if (h) return l(h);
|
|
418
|
+
if (g.has(n)) return e[n];
|
|
419
|
+
},
|
|
420
|
+
set: (r, n, h) => {
|
|
421
|
+
if (typeof n != "string") return !1;
|
|
422
|
+
const m = i.children.get(n);
|
|
423
|
+
return m ? (m.set(h), !0) : !1;
|
|
424
|
+
},
|
|
425
|
+
has: (r, n) => typeof n == "string" && (i.children.has(n) || g.has(n)),
|
|
426
|
+
ownKeys: () => [...i.children.keys()],
|
|
427
|
+
getOwnPropertyDescriptor: (r, n) => typeof n == "string" && i.children.has(n) ? { enumerable: !0, configurable: !0 } : void 0
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
return y.set(i, s), s;
|
|
431
|
+
}
|
|
432
|
+
function S(i) {
|
|
433
|
+
const t = new b(i, "");
|
|
434
|
+
return A(t);
|
|
435
|
+
}
|
|
436
|
+
export {
|
|
437
|
+
P as S,
|
|
438
|
+
S as c
|
|
439
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export { useFormControl } from './hooks/useFormControl';
|
|
13
|
-
export { useFormControlValue } from './hooks/useFormControlValue';
|
|
14
|
-
export { useArrayLength } from './hooks/useArrayLength';
|
|
15
|
-
export type { FieldControlState, ArrayControlState } from './hooks/types';
|
|
16
|
-
export * as validators from './core/validation';
|
|
1
|
+
/**
|
|
2
|
+
* Публичный barrel `@reformer/core` — зонтик над модулями `model` (state) и `form`.
|
|
3
|
+
*
|
|
4
|
+
* Состав экспортов НЕ изменился при модуляризации (реорг в src/model + src/form): реализация
|
|
5
|
+
* разложена по модулям, а этот barrel по-прежнему отдаёт единую поверхность.
|
|
6
|
+
*/
|
|
7
|
+
export * from './form/types/index';
|
|
8
|
+
export * from './form/factories/index';
|
|
9
|
+
export * from './state/index';
|
|
10
|
+
export * from './form/index';
|
|
11
|
+
export * as validators from './form/validation/index';
|