@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,7 +1,7 @@
|
|
|
1
|
-
import { FormProxy, GroupNodeConfig, FormSchema } from '
|
|
2
|
-
import { FormSchemaNode } from '
|
|
3
|
-
import { FormModel } from '../
|
|
4
|
-
import { FormBehavior } from '
|
|
1
|
+
import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
|
|
2
|
+
import { FormSchemaNode } from './types/schema-node';
|
|
3
|
+
import { FormModel } from '../state/types';
|
|
4
|
+
import { FormBehavior } from './behaviors';
|
|
5
5
|
/**
|
|
6
6
|
* Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
|
|
7
7
|
* конфиг полей (component/componentProps/validators) — из единой схемы.
|
|
@@ -52,6 +52,7 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
52
52
|
* component: Section,
|
|
53
53
|
* children: [
|
|
54
54
|
* { value: model.$.email, component: Input, validators: [required] },
|
|
55
|
+
* // вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
|
|
55
56
|
* { value: model.$.profile.name, component: Input },
|
|
56
57
|
* ],
|
|
57
58
|
* };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormNode } from '
|
|
2
|
-
import { FormProxy } from '
|
|
1
|
+
import { FormNode } from './nodes/form-node';
|
|
2
|
+
import { FormProxy } from './types/form-proxy';
|
|
3
3
|
/**
|
|
4
4
|
* Создать Proxy для типобезопасного доступа к полям GroupNode
|
|
5
5
|
*
|
|
@@ -70,7 +70,12 @@ export declare class FormSubmitter<T extends object> {
|
|
|
70
70
|
*
|
|
71
71
|
* @param onSubmit - Callback для отправки данных
|
|
72
72
|
* @param options - Опции submit
|
|
73
|
-
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
73
|
+
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Возвращаемый `null` неоднозначен: он сигналит и провал валидации, и легитимный `null`,
|
|
77
|
+
* который вернул `onSubmit` (или void-обработчик). Чтобы различать эти случаи, используйте
|
|
78
|
+
* {@link submitWithResult} — он отдаёт явный флаг `success`.
|
|
74
79
|
*
|
|
75
80
|
* @example
|
|
76
81
|
* ```typescript
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ValidationError } from '../
|
|
1
|
+
import { ValidationError } from '../types/index';
|
|
2
2
|
/**
|
|
3
3
|
* Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FieldNode}.
|
|
4
4
|
*
|
|
@@ -191,6 +191,20 @@ export interface FieldControlState<T> {
|
|
|
191
191
|
* ```
|
|
192
192
|
*/
|
|
193
193
|
componentProps: Record<string, unknown>;
|
|
194
|
+
/**
|
|
195
|
+
* Флаг изменения поля.
|
|
196
|
+
* `true` когда значение поля отличается от начального.
|
|
197
|
+
*
|
|
198
|
+
* Реактивный аналог `control.dirty` — паритет с {@link ArrayControlState.dirty}.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* const { dirty } = useFormControl(field);
|
|
203
|
+
*
|
|
204
|
+
* return dirty ? <span className="badge">Изменено</span> : null;
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
dirty: boolean;
|
|
194
208
|
}
|
|
195
209
|
/**
|
|
196
210
|
* Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.
|
|
@@ -325,4 +339,20 @@ export interface ArrayControlState<T> {
|
|
|
325
339
|
* ```
|
|
326
340
|
*/
|
|
327
341
|
dirty: boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Флаг отключения массива.
|
|
344
|
+
* `true` когда массив отключён (`ArrayNode.disable()`), в том числе через
|
|
345
|
+
* распространение disable от родительской группы.
|
|
346
|
+
*
|
|
347
|
+
* Используйте для отключения UI-действий добавления/удаления, когда массив
|
|
348
|
+
* структурно неизменяем.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```tsx
|
|
352
|
+
* const { disabled } = useFormControl(itemsArray);
|
|
353
|
+
*
|
|
354
|
+
* return <button disabled={disabled} onClick={() => control.push()}>Add</button>;
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
disabled: boolean;
|
|
328
358
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FieldNode } from '../
|
|
2
|
-
import { ArrayNode } from '../
|
|
3
|
-
import { FormValue } from '../
|
|
1
|
+
import { FieldNode } from '../nodes/field-node';
|
|
2
|
+
import { ArrayNode } from '../nodes/array-node';
|
|
3
|
+
import { FormValue } from '../types/index';
|
|
4
4
|
import { FieldControlState, ArrayControlState } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* React-хук для подписки на состояние {@link ArrayNode}.
|
|
@@ -14,4 +14,11 @@ export interface SignalConfig<K extends string> {
|
|
|
14
14
|
* Хук для подписки на набор сигналов с кешированием
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
|
-
export declare function useSignalSubscription<TSignals extends SignalMap, TSnapshot extends ExtractSignalValues<TSignals>>(signals: TSignals, configs: SignalConfig<Extract<keyof TSignals, string>>[], buildSnapshot: (values: ExtractSignalValues<TSignals>) => TSnapshot
|
|
17
|
+
export declare function useSignalSubscription<TSignals extends SignalMap, TSnapshot extends ExtractSignalValues<TSignals>>(signals: TSignals, configs: SignalConfig<Extract<keyof TSignals, string>>[], buildSnapshot: (values: ExtractSignalValues<TSignals>) => TSnapshot,
|
|
18
|
+
/**
|
|
19
|
+
* Стабильная идентичность подписки (обычно сам узел-контрол). subscribe/getSnapshot
|
|
20
|
+
* пересоздаются ТОЛЬКО при её смене, а не на каждом рендере — как у useFormControlValue,
|
|
21
|
+
* который ключует по `[control]`. Это убирает per-render churn (dispose+recreate preact-effect),
|
|
22
|
+
* сохраняя реактивность при подмене контрола на другой узел.
|
|
23
|
+
*/
|
|
24
|
+
subscriptionKey?: unknown): TSnapshot;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль `form` — доменный слой формы поверх state-субстрата (`model`).
|
|
3
|
+
*
|
|
4
|
+
* Ноды (value/touched/dirty/status/errors/componentProps), сборка формы из модели по идентичности
|
|
5
|
+
* сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), in-form роутинг
|
|
6
|
+
* валидации (`validateFormModel`), submit/статус, React-хуки. Зависит от `model` (form→state
|
|
7
|
+
* разрешено); обратной зависимости нет.
|
|
8
|
+
*
|
|
9
|
+
* @group Form
|
|
10
|
+
* @module form
|
|
11
|
+
*/
|
|
12
|
+
export { FormNode } from './nodes/form-node';
|
|
13
|
+
export type { SetValueOptions } from './nodes/form-node';
|
|
14
|
+
export { FieldNode } from './nodes/field-node';
|
|
15
|
+
export { GroupNode } from './nodes/group-node';
|
|
16
|
+
export { ArrayNode } from './nodes/array-node';
|
|
17
|
+
export { ModelArrayNode } from './nodes/model-array-node';
|
|
18
|
+
export type { ModelArrayControl } from './nodes/model-array-node';
|
|
19
|
+
export { createForm, createFormFromModel } from './create-form';
|
|
20
|
+
export type { CreateFormFromModelArgs } from './create-form';
|
|
21
|
+
export { validateModel, validateModelSync } from './validate-model-core';
|
|
22
|
+
export type { ModelValidator, ModelValidationResult } from './validate-model-core';
|
|
23
|
+
export { validateFormModel } from './validate-model';
|
|
24
|
+
export { enableWhen, disableWhen } from './behaviors-node';
|
|
25
|
+
export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
26
|
+
export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
|
|
27
|
+
export { FormSubmitter } from './form-submitter';
|
|
28
|
+
export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
|
|
29
|
+
export { FormStatusMachine } from './status-machine';
|
|
30
|
+
export type { StatusEvent } from './status-machine';
|
|
31
|
+
export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
|
|
32
|
+
export { uniqueId } from './unique-id';
|
|
33
|
+
export { useFormControl } from './hooks/useFormControl';
|
|
34
|
+
export { useFormControlValue } from './hooks/useFormControlValue';
|
|
35
|
+
export { useArrayLength } from './hooks/useArrayLength';
|
|
36
|
+
export type { FieldControlState, ArrayControlState } from './hooks/types';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode, SetValueOptions } from './form-node';
|
|
3
|
-
import { FieldStatus, ValidationError } from '../types';
|
|
3
|
+
import { FieldStatus, ValidationError } from '../types/index';
|
|
4
4
|
import { FormSchema } from '../types/deep-schema';
|
|
5
5
|
import { FormProxy } from '../types/form-proxy';
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode, SetValueOptions } from './form-node';
|
|
3
|
-
import { FieldConfig, FieldStatus, ValidationError } from '../types';
|
|
3
|
+
import { FieldConfig, FieldStatus, ValidationError } from '../types/index';
|
|
4
4
|
/**
|
|
5
5
|
* FieldNode - узел для отдельного поля формы
|
|
6
6
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReadonlySignal, Signal } from '@preact/signals-core';
|
|
2
|
-
import { FieldStatus, ValidationError, ErrorFilterOptions } from '../types';
|
|
2
|
+
import { FieldStatus, ValidationError, ErrorFilterOptions } from '../types/index';
|
|
3
3
|
/**
|
|
4
4
|
* Опции для setValue
|
|
5
5
|
* @group Nodes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ReadonlySignal } from '@preact/signals-core';
|
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode, SetValueOptions } from './form-node';
|
|
3
|
-
import { ValidationError, FieldStatus, FormSchema, GroupNodeConfig, FormValue } from '../types';
|
|
3
|
+
import { ValidationError, FieldStatus, FormSchema, GroupNodeConfig, FormValue } from '../types/index';
|
|
4
4
|
import { FormProxy } from '../types/form-proxy';
|
|
5
|
-
import { SubmitOptions, SubmitResult } from '../
|
|
5
|
+
import { SubmitOptions, SubmitResult } from '../form-submitter';
|
|
6
6
|
/**
|
|
7
7
|
* GroupNode - узел для группы полей
|
|
8
8
|
*
|
|
@@ -54,6 +54,18 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
54
54
|
private readonly _disabled;
|
|
55
55
|
/** Form-level validation errors */
|
|
56
56
|
private readonly _formErrors;
|
|
57
|
+
/**
|
|
58
|
+
* M1: валидация уровня модели/схемы (`validateFormModel(model, schema)`). Привязывается
|
|
59
|
+
* `createForm({ model, schema })`, т.к. под M1 валидаторы срезаны с FieldNode и живут на слое
|
|
60
|
+
* модели. Без неё `validate()`/`submit()` пропускали бы все schema-валидаторы. @internal
|
|
61
|
+
*/
|
|
62
|
+
private _modelValidate?;
|
|
63
|
+
/**
|
|
64
|
+
* M1: связь листовой ноды-ребёнка → её сигнал модели (тот, что помечает `markDerived`).
|
|
65
|
+
* Нужна bulk-сеттерам (`setValue`/`patchValue`), чтобы корректно определять derived-поля:
|
|
66
|
+
* `field.value` — computed-обёртка, отличная от записываемого сигнала модели. @internal
|
|
67
|
+
*/
|
|
68
|
+
private readonly _fieldSignals;
|
|
57
69
|
readonly value: ReadonlySignal<T>;
|
|
58
70
|
readonly valid: ReadonlySignal<boolean>;
|
|
59
71
|
readonly invalid: ReadonlySignal<boolean>;
|
|
@@ -156,7 +168,12 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
156
168
|
*
|
|
157
169
|
* @param onSubmit - Callback для отправки данных
|
|
158
170
|
* @param options - Опции submit (skipValidation, skipTouch)
|
|
159
|
-
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
171
|
+
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* `null` перегружен: он означает и «валидация не пройдена», и легитимный `null`-результат
|
|
175
|
+
* `onSubmit` (или void-обработчик). Если вызывающей стороне нужно различать эти случаи —
|
|
176
|
+
* используйте {@link submitWithResult}, который возвращает явный флаг `success`.
|
|
160
177
|
*/
|
|
161
178
|
submit<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<R | null>;
|
|
162
179
|
/**
|
|
@@ -264,6 +281,19 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
264
281
|
* @internal
|
|
265
282
|
*/
|
|
266
283
|
attachBehaviorCleanup(cleanup: () => void): void;
|
|
284
|
+
/**
|
|
285
|
+
* Прикрепить валидацию уровня модели/схемы (M1). Вызывается `createForm({ model, schema })`,
|
|
286
|
+
* чтобы `validate()`/`submit()` прогоняли schema-валидаторы (`validateFormModel`), а не только
|
|
287
|
+
* пустые FieldNode. Возвращаемый флаг — валидна ли модель по схеме.
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
attachModelValidator(validate: () => Promise<boolean>): void;
|
|
291
|
+
/**
|
|
292
|
+
* Связать листовую ноду-ребёнка с её сигналом модели (M1). Вызывается `createForm` для каждого
|
|
293
|
+
* листового поля на его владеющей группе — используется bulk-сеттерами для derived-guard (F9).
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
registerFieldSignal(node: FormNode<any>, signal: Signal<unknown>): void;
|
|
267
297
|
/**
|
|
268
298
|
* Очистить все ресурсы узла
|
|
269
299
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode } from './form-node';
|
|
3
|
-
import { FieldStatus, ValidationError } from '../types';
|
|
4
|
-
import { FormModel } from '
|
|
3
|
+
import { FieldStatus, ValidationError } from '../types/index';
|
|
4
|
+
import { FormModel } from '../../state/types';
|
|
5
5
|
import { FormProxy } from '../types/form-proxy';
|
|
6
6
|
/** Минимальный контракт реактивного массива модели, используемый узлом. */
|
|
7
7
|
export interface ModelArrayControl<TItem extends object> {
|
|
@@ -58,6 +58,7 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
58
58
|
private readonly buildItem;
|
|
59
59
|
private readonly itemNodes;
|
|
60
60
|
private readonly cache;
|
|
61
|
+
private mountedModels;
|
|
61
62
|
private readonly initial;
|
|
62
63
|
private readonly _arrayErrors;
|
|
63
64
|
private readonly disposeSync;
|
|
@@ -109,6 +110,12 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
109
110
|
protected onMarkAsPristine(): void;
|
|
110
111
|
protected onDisable(): void;
|
|
111
112
|
protected onEnable(): void;
|
|
112
|
-
/**
|
|
113
|
+
/** Диспозит форму элемента (эффекты/поведения/вложенные подписки); терпима к не-GroupNode. */
|
|
114
|
+
private disposeItemNode;
|
|
115
|
+
/**
|
|
116
|
+
* Очистка подписки синхронизации И детерминированный teardown всех форм элементов.
|
|
117
|
+
* Без пробрасывания dispose в itemNodes эффекты/поведения каждой формы элемента (в т.ч. disposeSync
|
|
118
|
+
* вложенных ModelArrayNode) остались бы жить после teardown формы — утечка подписок.
|
|
119
|
+
*/
|
|
113
120
|
dispose(): void;
|
|
114
121
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { FormNode } from '
|
|
2
|
-
import { FieldNode } from '
|
|
3
|
-
import { GroupNode } from '
|
|
4
|
-
import { ArrayNode } from '
|
|
5
|
-
import { FormValue } from '
|
|
1
|
+
import { FormNode } from './nodes/form-node';
|
|
2
|
+
import { FieldNode } from './nodes/field-node';
|
|
3
|
+
import { GroupNode } from './nodes/group-node';
|
|
4
|
+
import { ArrayNode } from './nodes/array-node';
|
|
5
|
+
import { FormValue } from './types/index';
|
|
6
6
|
/**
|
|
7
7
|
* Проверить, является ли значение любым FormNode
|
|
8
8
|
*
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Общие контракт-типы значения и валидации (state-слой).
|
|
3
|
+
*
|
|
4
|
+
* Эти типы нужны и headless-движку валидации данных (`validate-model-core`, `schema-node`,
|
|
5
|
+
* `error-handler` — state), и form-слою. Живут в `model/`, поэтому state их импортирует напрямую,
|
|
6
|
+
* а form-слой реэкспортирует из `form/types` (form→state разрешено). Так граница state⇏form не
|
|
7
|
+
* нарушается: общие контракты принадлежат нижнему слою.
|
|
8
|
+
*
|
|
9
|
+
* @group Types
|
|
10
|
+
* @module model/contracts
|
|
11
|
+
*/
|
|
3
12
|
/**
|
|
4
13
|
* Represents any valid form value type
|
|
5
14
|
* Use this instead of 'any' for form values to maintain type safety
|
|
@@ -95,91 +104,3 @@ export interface ErrorFilterOptions {
|
|
|
95
104
|
* @category Core Types
|
|
96
105
|
*/
|
|
97
106
|
export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
|
|
98
|
-
export type { FieldConfig } from './deep-schema';
|
|
99
|
-
export type { Validator, AsyncValidator, ConditionFn, ValidateOptions, ValidateAsyncOptions, } from './validation-schema';
|
|
100
|
-
export type { FormSchema, ArrayConfig } from './deep-schema';
|
|
101
|
-
export type { FormSchemaNode, SchemaArrayControl, SchemaValidator } from './schema-node';
|
|
102
|
-
export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
|
|
103
|
-
/**
|
|
104
|
-
* Конфигурация GroupNode.
|
|
105
|
-
*
|
|
106
|
-
* Под M1 группа создаётся из плоской {@link FormSchema} (дерево field-конфигов). Обёртка
|
|
107
|
-
* `{ form }` сохранена для совместимости вызова, legacy behavior/validation-схемы удалены (Ф7).
|
|
108
|
-
*
|
|
109
|
-
* @group Types
|
|
110
|
-
* @category Configuration Types
|
|
111
|
-
*/
|
|
112
|
-
export interface GroupNodeConfig<T> {
|
|
113
|
-
/** Схема структуры формы (поля и их конфигурация) */
|
|
114
|
-
form: FormSchema<T>;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Тип для Record с unknown значениями
|
|
118
|
-
* Используется вместо инлайнового `Record<string, unknown>`
|
|
119
|
-
* @internal
|
|
120
|
-
*/
|
|
121
|
-
export type UnknownRecord = Record<string, unknown>;
|
|
122
|
-
/**
|
|
123
|
-
* Интерфейс для узлов с методом applyValidationSchema
|
|
124
|
-
* @internal
|
|
125
|
-
*/
|
|
126
|
-
export interface WithValidationSchema {
|
|
127
|
-
applyValidationSchema(schemaFn: unknown): void;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Интерфейс для узлов с методом applyBehaviorSchema
|
|
131
|
-
* @internal
|
|
132
|
-
*/
|
|
133
|
-
export interface WithBehaviorSchema {
|
|
134
|
-
applyBehaviorSchema(schemaFn: unknown): void;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Интерфейс для узлов, похожих на ArrayNode (с методом at)
|
|
138
|
-
* Используется для duck typing при обходе путей
|
|
139
|
-
* @internal
|
|
140
|
-
*/
|
|
141
|
-
export interface ArrayNodeLike {
|
|
142
|
-
at(index: number): FormNode<unknown> | undefined;
|
|
143
|
-
length: unknown;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Конфиг с полем schema (для ArrayConfig)
|
|
147
|
-
* @internal
|
|
148
|
-
*/
|
|
149
|
-
export interface ConfigWithSchema {
|
|
150
|
-
schema: unknown;
|
|
151
|
-
initialItems?: unknown[];
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Конфиг с полем value (для извлечения значений)
|
|
155
|
-
* @internal
|
|
156
|
-
*/
|
|
157
|
-
export interface ConfigWithValue {
|
|
158
|
-
value: unknown;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Тип для путей к полям (field paths)
|
|
162
|
-
* Используется в навигации по полям вместо any
|
|
163
|
-
* @internal
|
|
164
|
-
*/
|
|
165
|
-
export type FieldPathSegment = {
|
|
166
|
-
key: string;
|
|
167
|
-
index?: number;
|
|
168
|
-
};
|
|
169
|
-
/**
|
|
170
|
-
* Тип для коллбэков и обработчиков событий
|
|
171
|
-
* Используется вместо (...args: any[]) => any
|
|
172
|
-
* @internal
|
|
173
|
-
*/
|
|
174
|
-
export type UnknownCallback = (...args: unknown[]) => unknown;
|
|
175
|
-
/**
|
|
176
|
-
* Тип для проверки на функцию в conditional types
|
|
177
|
-
* Используется вместо Function для type narrowing
|
|
178
|
-
* @internal
|
|
179
|
-
*/
|
|
180
|
-
export type AnyFunction = (...args: never[]) => unknown;
|
|
181
|
-
/**
|
|
182
|
-
* Тип для результатов загрузки ресурсов
|
|
183
|
-
* @internal
|
|
184
|
-
*/
|
|
185
|
-
export type ResourceLoadResult = unknown;
|
|
@@ -37,6 +37,27 @@ export interface FieldConfig<T> {
|
|
|
37
37
|
/** Задержка (в мс) перед запуском асинхронной валидации */
|
|
38
38
|
debounce?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* «Хотя бы одно поле обязано присутствовать»: превращает объект со всеми опциональными
|
|
42
|
+
* свойствами в объединение вариантов, где ровно один ключ обязателен, а остальные опциональны.
|
|
43
|
+
* Пустой объект `{}` не assignable ни к одному варианту и потому отклоняется.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
type RequireAtLeastOne<T> = {
|
|
48
|
+
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
|
|
49
|
+
}[keyof T];
|
|
50
|
+
/**
|
|
51
|
+
* {@link FieldConfig}, из которого нельзя сделать пустой `{}`.
|
|
52
|
+
*
|
|
53
|
+
* Все свойства {@link FieldConfig} опциональны, поэтому в {@link FormSchema} опечатка вида
|
|
54
|
+
* `{ email: {} }` молча проходит проверку типов и в рантайме становится пустой подгруппой
|
|
55
|
+
* вместо поля. `NonEmptyFieldConfig` требует наличия хотя бы одного свойства (value/valueSignal/
|
|
56
|
+
* component/validators/…), оставляя каждое из них опциональным по отдельности.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
|
|
40
61
|
/**
|
|
41
62
|
* Конфигурация массива
|
|
42
63
|
* @group Types
|
|
@@ -90,5 +111,6 @@ export interface ArrayConfig<T extends object> {
|
|
|
90
111
|
* ```
|
|
91
112
|
*/
|
|
92
113
|
export type FormSchema<T> = {
|
|
93
|
-
[K in keyof T]: NonNullable<T[K]> extends string | number | boolean ?
|
|
114
|
+
[K in keyof T]: NonNullable<T[K]> extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : NonNullable<T[K]> extends Array<infer U> ? U extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : U extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : [FormSchema<U>] : NonNullable<T[K]> extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : FormSchema<NonNullable<T[K]>>;
|
|
94
115
|
};
|
|
116
|
+
export {};
|
|
@@ -64,8 +64,33 @@ export type FormControlsProxy<T> = {
|
|
|
64
64
|
* form.email.setValue('test@mail.com');
|
|
65
65
|
* form.profile.name.setValue('John');
|
|
66
66
|
* ```
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* **Зарезервированные имена.** Прямой доступ `form.<name>` отдаёт приоритет собственным
|
|
70
|
+
* членам {@link GroupNode} (`value`, `status`, `id`, `errors`, `disabled`, `reset`, `submit`, …).
|
|
71
|
+
* Поэтому поле модели, названное любым из этих имён, недостижимо как `form.<name>` — там будет
|
|
72
|
+
* агрегированный сигнал/метод узла, а не {@link FieldNode}. Такие имена исключены из полевого
|
|
73
|
+
* пространства имён на уровне типов (через `Omit<…, keyof GroupNode>`), чтобы затенение не
|
|
74
|
+
* компилировалось молча. Для доступа к любому полю по имени — включая затенённые — используйте
|
|
75
|
+
* escape-hatch пространство {@link FormProxy.$}: `form.$.status`, `form.$.id`, `form.$.value`.
|
|
67
76
|
*/
|
|
68
|
-
export type FormProxy<T> = GroupNode<T> & FormControlsProxy<T
|
|
77
|
+
export type FormProxy<T> = GroupNode<T> & Omit<FormControlsProxy<T>, keyof GroupNode<T>> & {
|
|
78
|
+
/**
|
|
79
|
+
* Escape-hatch пространство имён «controls»: типобезопасный доступ ко ВСЕМ полям формы
|
|
80
|
+
* по имени, включая поля, чьи имена совпадают с членами {@link GroupNode}
|
|
81
|
+
* (`value`/`status`/`id`/`errors`/…), недостижимые через `form.<name>`.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // модель: { status: string; email: string }
|
|
86
|
+
* form.status; // ReadonlySignal<FieldStatus> — агрегат GroupNode (не поле!)
|
|
87
|
+
* form.$.status; // FieldNode<string> — поле пользователя
|
|
88
|
+
* form.$.status.setValue('active');
|
|
89
|
+
* form.$.email; // FieldNode<string> — так же доступны и незатенённые поля
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
readonly $: FormControlsProxy<T>;
|
|
93
|
+
};
|
|
69
94
|
/**
|
|
70
95
|
* Комбинированный тип для ArrayNode с Proxy доступом к элементам
|
|
71
96
|
*
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { FormSchema } from './deep-schema';
|
|
2
|
+
import { FormNode } from '../nodes/form-node';
|
|
3
|
+
export type { FormValue, UnknownFormValue, ValidatorFn, AsyncValidatorOptions, AsyncValidatorFn, ValidationError, ErrorFilterOptions, FieldStatus, } from './contracts';
|
|
4
|
+
export type { FieldConfig } from './deep-schema';
|
|
5
|
+
export type { Validator, AsyncValidator, ConditionFn, ValidateOptions, ValidateAsyncOptions, } from './validation-schema';
|
|
6
|
+
export type { FormSchema, ArrayConfig } from './deep-schema';
|
|
7
|
+
export type { FormSchemaNode, SchemaArrayControl, SchemaValidator } from './schema-node';
|
|
8
|
+
export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
|
|
9
|
+
/**
|
|
10
|
+
* Конфигурация GroupNode.
|
|
11
|
+
*
|
|
12
|
+
* Под M1 группа создаётся из плоской {@link FormSchema} (дерево field-конфигов). Обёртка
|
|
13
|
+
* `{ form }` сохранена для совместимости вызова, legacy behavior/validation-схемы удалены (Ф7).
|
|
14
|
+
*
|
|
15
|
+
* @group Types
|
|
16
|
+
* @category Configuration Types
|
|
17
|
+
*/
|
|
18
|
+
export interface GroupNodeConfig<T> {
|
|
19
|
+
/** Схема структуры формы (поля и их конфигурация) */
|
|
20
|
+
form: FormSchema<T>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Тип для Record с unknown значениями
|
|
24
|
+
* Используется вместо инлайнового `Record<string, unknown>`
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export type UnknownRecord = Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Интерфейс для узлов с методом applyValidationSchema
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export interface WithValidationSchema {
|
|
33
|
+
applyValidationSchema(schemaFn: unknown): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Интерфейс для узлов с методом applyBehaviorSchema
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export interface WithBehaviorSchema {
|
|
40
|
+
applyBehaviorSchema(schemaFn: unknown): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Интерфейс для узлов, похожих на ArrayNode (с методом at)
|
|
44
|
+
* Используется для duck typing при обходе путей
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export interface ArrayNodeLike {
|
|
48
|
+
at(index: number): FormNode<unknown> | undefined;
|
|
49
|
+
length: unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Конфиг с полем schema (для ArrayConfig)
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export interface ConfigWithSchema {
|
|
56
|
+
schema: unknown;
|
|
57
|
+
initialItems?: unknown[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Конфиг с полем value (для извлечения значений)
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export interface ConfigWithValue {
|
|
64
|
+
value: unknown;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Тип для путей к полям (field paths)
|
|
68
|
+
* Используется в навигации по полям вместо any
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export type FieldPathSegment = {
|
|
72
|
+
key: string;
|
|
73
|
+
index?: number;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Тип для коллбэков и обработчиков событий
|
|
77
|
+
* Используется вместо (...args: any[]) => any
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export type UnknownCallback = (...args: unknown[]) => unknown;
|
|
81
|
+
/**
|
|
82
|
+
* Тип для проверки на функцию в conditional types
|
|
83
|
+
* Используется вместо Function для type narrowing
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export type AnyFunction = (...args: never[]) => unknown;
|
|
87
|
+
/**
|
|
88
|
+
* Тип для результатов загрузки ресурсов
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
export type ResourceLoadResult = unknown;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
|
-
import { ValidationError } from './
|
|
2
|
+
import { ValidationError } from './contracts';
|
|
3
3
|
/**
|
|
4
4
|
* Валидатор поля схемы. Узел хранит validators гетерогенно: движок `validate*` вызывает их как
|
|
5
5
|
* `(value, scope, root)`, но сюда кладут любой контракт — `ValidatorFn` (value),
|