@reformer/core 5.0.0 → 6.0.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 +7 -5
- package/dist/behaviors-O9a1Djj9.js +147 -0
- package/dist/behaviors.d.ts +131 -4
- package/dist/behaviors.js +298 -27
- package/dist/core/model/behaviors.d.ts +123 -0
- package/dist/core/model/form-model.d.ts +18 -0
- package/dist/core/model/index.d.ts +12 -0
- package/dist/core/model/types.d.ts +112 -0
- package/dist/core/model/validate-model.d.ts +46 -0
- package/dist/core/nodes/array-node.d.ts +34 -2
- package/dist/core/nodes/group-node.d.ts +19 -71
- package/dist/core/nodes/model-array-node.d.ts +71 -0
- package/dist/core/types/deep-schema.d.ts +16 -2
- package/dist/core/types/form-proxy.d.ts +17 -6
- package/dist/core/types/index.d.ts +5 -23
- package/dist/core/types/validation-schema.d.ts +15 -66
- package/dist/core/utils/create-form.d.ts +24 -0
- package/dist/core/utils/derived-registry.d.ts +5 -0
- package/dist/core/utils/index.d.ts +4 -4
- package/dist/core/utils/signal-node-registry.d.ts +12 -0
- package/dist/core/utils/type-guards.d.ts +2 -3
- package/dist/core/validation/index.d.ts +5 -11
- package/dist/core/validation/validators/email.d.ts +5 -29
- package/dist/core/validation/validators/future-date.d.ts +6 -28
- package/dist/core/validation/validators/index.d.ts +11 -4
- package/dist/core/validation/validators/integer.d.ts +13 -0
- package/dist/core/validation/validators/is-date.d.ts +5 -28
- package/dist/core/validation/validators/is-number.d.ts +14 -0
- package/dist/core/validation/validators/max-age.d.ts +6 -29
- package/dist/core/validation/validators/max-date.d.ts +6 -29
- package/dist/core/validation/validators/max-length.d.ts +5 -30
- package/dist/core/validation/validators/max.d.ts +7 -29
- package/dist/core/validation/validators/min-age.d.ts +6 -29
- package/dist/core/validation/validators/min-date.d.ts +6 -29
- package/dist/core/validation/validators/min-length.d.ts +5 -30
- package/dist/core/validation/validators/min.d.ts +7 -29
- package/dist/core/validation/validators/multiple-of.d.ts +13 -0
- package/dist/core/validation/validators/non-negative.d.ts +13 -0
- package/dist/core/validation/validators/non-zero.d.ts +13 -0
- package/dist/core/validation/validators/past-date.d.ts +5 -28
- package/dist/core/validation/validators/pattern.d.ts +5 -32
- package/dist/core/validation/validators/phone.d.ts +9 -19
- package/dist/core/validation/validators/required.d.ts +10 -31
- package/dist/core/validation/validators/url.d.ts +13 -15
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1299 -875
- package/dist/validators/email.js +7 -9
- package/dist/validators/future-date.js +11 -12
- package/dist/validators/integer.d.ts +2 -0
- package/dist/validators/integer.js +10 -0
- package/dist/validators/is-date.js +6 -7
- package/dist/validators/is-number.d.ts +2 -0
- package/dist/validators/is-number.js +10 -0
- package/dist/validators/max-age.js +11 -12
- package/dist/validators/max-date.js +11 -12
- package/dist/validators/max-length.js +12 -8
- package/dist/validators/max.js +5 -6
- package/dist/validators/min-age.js +10 -11
- package/dist/validators/min-date.js +12 -13
- package/dist/validators/min-length.js +12 -8
- package/dist/validators/min.js +6 -7
- package/dist/validators/multiple-of.d.ts +2 -0
- package/dist/validators/multiple-of.js +10 -0
- package/dist/validators/non-negative.d.ts +2 -0
- package/dist/validators/non-negative.js +10 -0
- package/dist/validators/non-zero.d.ts +2 -0
- package/dist/validators/non-zero.js +10 -0
- package/dist/validators/past-date.js +11 -12
- package/dist/validators/pattern.js +6 -7
- package/dist/validators/phone.js +14 -32
- package/dist/validators/required.js +9 -10
- package/dist/validators/url.js +19 -16
- package/dist/validators.js +67 -54
- package/llms.txt +959 -2584
- package/package.json +19 -35
- package/dist/behaviors/compute-from.d.ts +0 -2
- package/dist/behaviors/compute-from.js +0 -31
- package/dist/behaviors/copy-from.d.ts +0 -2
- package/dist/behaviors/copy-from.js +0 -29
- package/dist/behaviors/enable-when.d.ts +0 -2
- package/dist/behaviors/enable-when.js +0 -25
- package/dist/behaviors/reset-when.d.ts +0 -2
- package/dist/behaviors/reset-when.js +0 -24
- package/dist/behaviors/revalidate-when.d.ts +0 -2
- package/dist/behaviors/revalidate-when.js +0 -18
- package/dist/behaviors/sync-fields.d.ts +0 -2
- package/dist/behaviors/sync-fields.js +0 -41
- package/dist/behaviors/transform-value.d.ts +0 -2
- package/dist/behaviors/transform-value.js +0 -45
- package/dist/behaviors/watch-field.d.ts +0 -2
- package/dist/behaviors/watch-field.js +0 -21
- package/dist/core/behavior/behavior-context.d.ts +0 -47
- package/dist/core/behavior/behavior-registry.d.ts +0 -85
- package/dist/core/behavior/behaviors/compute-from.d.ts +0 -70
- package/dist/core/behavior/behaviors/copy-from.d.ts +0 -56
- package/dist/core/behavior/behaviors/enable-when.d.ts +0 -118
- package/dist/core/behavior/behaviors/index.d.ts +0 -11
- package/dist/core/behavior/behaviors/reset-when.d.ts +0 -64
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +0 -53
- package/dist/core/behavior/behaviors/sync-fields.d.ts +0 -48
- package/dist/core/behavior/behaviors/transform-value.d.ts +0 -192
- package/dist/core/behavior/behaviors/watch-field.d.ts +0 -80
- package/dist/core/behavior/compose-behavior.d.ts +0 -96
- package/dist/core/behavior/index.d.ts +0 -11
- package/dist/core/behavior/types.d.ts +0 -146
- package/dist/core/types/field-path.d.ts +0 -42
- package/dist/core/types/form-context.d.ts +0 -89
- package/dist/core/utils/field-path-navigator.d.ts +0 -240
- package/dist/core/utils/field-path.d.ts +0 -60
- package/dist/core/utils/registry-helpers.d.ts +0 -43
- package/dist/core/validation/core/apply-when.d.ts +0 -22
- package/dist/core/validation/core/apply.d.ts +0 -52
- package/dist/core/validation/core/index.d.ts +0 -8
- package/dist/core/validation/core/validate-async.d.ts +0 -36
- package/dist/core/validation/core/validate-tree.d.ts +0 -35
- package/dist/core/validation/core/validate.d.ts +0 -26
- package/dist/core/validation/validate-form.d.ts +0 -48
- package/dist/core/validation/validation-applicator.d.ts +0 -70
- package/dist/core/validation/validation-context.d.ts +0 -86
- package/dist/core/validation/validation-registry.d.ts +0 -142
- package/dist/core/validation/validators/array-validators.d.ts +0 -53
- package/dist/core/validation/validators/number.d.ts +0 -31
- package/dist/field-path-DuKdGcIE.js +0 -66
- package/dist/hooks/useHiddenCondition.d.ts +0 -25
- package/dist/index-D25LsbRm.js +0 -73
- package/dist/registry-helpers-Bv_BJ1s-.js +0 -615
- package/dist/safe-effect-Dh8uw81c.js +0 -20
- package/dist/validate-C3XiA_zf.js +0 -10
- package/dist/validators/number.d.ts +0 -2
- package/dist/validators/number.js +0 -35
- package/dist/validators-BGsNOgT1.js +0 -207
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# AUTO-GENERATED. Edit docs/llms/*.md or JSDoc in src/ and run npm run generate:llms.
|
|
3
3
|
|
|
4
4
|
> Reactive form state management library for React with signals-based architecture
|
|
5
|
-
> Package: @reformer/core • Version:
|
|
5
|
+
> Package: @reformer/core • Version: 5.0.2
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
- 01-api-reference.md — api-reference
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
|
|
43
43
|
### Imports (CRITICALLY IMPORTANT)
|
|
44
44
|
|
|
45
|
-
| What
|
|
46
|
-
|
|
|
47
|
-
| `createForm`, `useFormControl`, `useFormControlValue`, `validateForm`
|
|
45
|
+
| What | Where |
|
|
46
|
+
| ------------------------------------------------------------------------------------------- | --------------------------- |
|
|
47
|
+
| `createForm`, `useFormControl`, `useFormControlValue`, `validateForm` | `@reformer/core` |
|
|
48
48
|
| `ValidationSchemaFn`, `BehaviorSchemaFn`, `FieldPath`, `FormProxy`, `FieldNode` | `@reformer/core` |
|
|
49
|
-
| `FormSchema`, `FieldConfig`, `ArrayNode`
|
|
50
|
-
| `required`, `min`, `max`, `minLength`, `maxLength`, `email`
|
|
51
|
-
| `pattern`, `url`, `phone`, `
|
|
52
|
-
| `validate`, `validateAsync`, `
|
|
53
|
-
| `notEmpty`, `validateItems`
|
|
54
|
-
| `computeFrom`, `enableWhen`, `disableWhen`, `watchField`, `copyFrom`
|
|
55
|
-
| `resetWhen`, `revalidateWhen`, `syncFields`
|
|
56
|
-
| `transformValue`, `transformers`
|
|
49
|
+
| `FormSchema`, `FieldConfig`, `ArrayNode` | `@reformer/core` |
|
|
50
|
+
| `required`, `min`, `max`, `minLength`, `maxLength`, `email` | `@reformer/core/validators` |
|
|
51
|
+
| `pattern`, `url`, `phone`, `isNumber`, `integer`, `multipleOf`, `nonNegative`, `nonZero` | `@reformer/core/validators` |
|
|
52
|
+
| `validate`, `validateAsync`, `applyWhen`, `apply` | `@reformer/core/validators` |
|
|
53
|
+
| `notEmpty`, `validateItems` | `@reformer/core/validators` |
|
|
54
|
+
| `computeFrom`, `enableWhen`, `disableWhen`, `watchField`, `copyFrom` | `@reformer/core/behaviors` |
|
|
55
|
+
| `resetWhen`, `revalidateWhen`, `syncFields` | `@reformer/core/behaviors` |
|
|
56
|
+
| `transformValue`, `transformers` | `@reformer/core/behaviors` |
|
|
57
57
|
|
|
58
58
|
### Type Values
|
|
59
59
|
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
|
|
64
64
|
### React Hooks Comparison (CRITICALLY IMPORTANT)
|
|
65
65
|
|
|
66
|
-
| Hook
|
|
67
|
-
|
|
68
|
-
| `useFormControl(field)`
|
|
69
|
-
| `useFormControlValue(field)` | `T` (value directly)
|
|
66
|
+
| Hook | Return Type | Subscribes To | Use Case |
|
|
67
|
+
|------|-------------|---------------|----------|
|
|
68
|
+
| `useFormControl(field)` | `{ value, errors, disabled, touched, ... }` | All signals | Full field state, form inputs |
|
|
69
|
+
| `useFormControlValue(field)` | `T` (value directly) | Only value signal | Conditional rendering |
|
|
70
70
|
|
|
71
71
|
**CRITICAL**: Do NOT destructure `useFormControlValue`! It returns `T` directly, NOT `{ value: T }`.
|
|
72
72
|
|
|
@@ -188,26 +188,39 @@ const schema: FormSchema<{ properties: PropertyItem[] }> = {
|
|
|
188
188
|
|
|
189
189
|
### Validators
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
number
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
191
|
+
Operators register validators in the schema. Validator factories return pure
|
|
192
|
+
`Validator<TForm, TField>` functions of shape `(value, control, root) => ValidationError | null`.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// Operators (register validators in schema)
|
|
196
|
+
validate(path, validator: Validator<TForm, TField>, options?: { message?: string })
|
|
197
|
+
validateAsync(path, validator: AsyncValidator<TForm, TField>, options?: { message?: string; debounce?: number })
|
|
198
|
+
|
|
199
|
+
// Validator factories (return Validator). Pass to validate().
|
|
200
|
+
required(options?: { message?: string })
|
|
201
|
+
min(value: number, options?: { message?: string })
|
|
202
|
+
max(value: number, options?: { message?: string })
|
|
203
|
+
minLength(length: number, options?: { message?: string })
|
|
204
|
+
maxLength(length: number, options?: { message?: string })
|
|
205
|
+
email(options?: { message?: string })
|
|
206
|
+
pattern(regex: RegExp, options?: { message?: string })
|
|
207
|
+
url(options?: { message?: string; requireProtocol?: boolean })
|
|
208
|
+
phone(options?: { message?: string; format?: PhoneFormat })
|
|
209
|
+
// Number validator factories (atomic — compose via several validate() calls)
|
|
210
|
+
isNumber(options?: { message?: string })
|
|
211
|
+
integer(options?: { message?: string })
|
|
212
|
+
multipleOf(divisor: number, options?: { message?: string })
|
|
213
|
+
nonNegative(options?: { message?: string }) // value >= 0
|
|
214
|
+
nonZero(options?: { message?: string }) // value !== 0
|
|
215
|
+
date(options?: { message?: string; minAge?: number; maxAge?: number; noFuture?: boolean; noPast?: boolean })
|
|
216
|
+
notEmpty(options?: { message?: string })
|
|
217
|
+
|
|
218
|
+
// Usage: pass a factory result to validate()
|
|
219
|
+
validate(path.email, required());
|
|
220
|
+
validate(path.age, min(18));
|
|
221
|
+
validate(path.amount, integer());
|
|
222
|
+
validate(path.amount, min(0));
|
|
223
|
+
validate(path.amount, max(1000));
|
|
211
224
|
|
|
212
225
|
// Conditional validation (3 arguments!)
|
|
213
226
|
applyWhen(fieldPath, condition: (fieldValue) => boolean, validatorsFn: (path) => void)
|
|
@@ -220,8 +233,8 @@ applyWhen(
|
|
|
220
233
|
path.loanType, // 1st: field to watch
|
|
221
234
|
(type) => type === 'mortgage', // 2nd: condition on field value
|
|
222
235
|
(p) => { // 3rd: validators to apply
|
|
223
|
-
|
|
224
|
-
|
|
236
|
+
validate(p.propertyValue, required());
|
|
237
|
+
validate(p.propertyValue, min(100000));
|
|
225
238
|
}
|
|
226
239
|
);
|
|
227
240
|
|
|
@@ -230,8 +243,8 @@ applyWhen(
|
|
|
230
243
|
path.address.country,
|
|
231
244
|
(country) => country === 'US',
|
|
232
245
|
(p) => {
|
|
233
|
-
|
|
234
|
-
|
|
246
|
+
validate(p.address.state, required());
|
|
247
|
+
validate(p.address.zip, pattern(/^\d{5}(-\d{4})?$/));
|
|
235
248
|
}
|
|
236
249
|
);
|
|
237
250
|
|
|
@@ -240,19 +253,18 @@ applyWhen(
|
|
|
240
253
|
path.hasInsurance,
|
|
241
254
|
(has) => has === true,
|
|
242
255
|
(p) => {
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
validate(p.insuranceCompany, required());
|
|
257
|
+
validate(p.policyNumber, required());
|
|
245
258
|
}
|
|
246
259
|
);
|
|
247
260
|
|
|
248
261
|
// WRONG - only 2 arguments (React Hook Form pattern)
|
|
249
262
|
applyWhen(
|
|
250
263
|
(form) => form.loanType === 'mortgage', // WRONG!
|
|
251
|
-
() => {
|
|
264
|
+
() => { validate(path.propertyValue, required()); }
|
|
252
265
|
);
|
|
253
266
|
|
|
254
|
-
// Array
|
|
255
|
-
notEmpty(path, options?: { message?: string })
|
|
267
|
+
// Array item schema operator
|
|
256
268
|
validateItems(arrayPath, itemValidatorsFn: (itemPath) => void)
|
|
257
269
|
```
|
|
258
270
|
|
|
@@ -426,26 +438,29 @@ const { value } = useFormControl(form.field as FieldNode<ExpectedType>);
|
|
|
426
438
|
|
|
427
439
|
### Validation Priority (IMPORTANT)
|
|
428
440
|
|
|
429
|
-
**
|
|
441
|
+
**Operators register validators; built-in factories return validators. Pass factories to `validate()`.**
|
|
442
|
+
|
|
443
|
+
Operators: `validate`, `validateAsync`, `applyWhen`, `apply`, `validateItems`.
|
|
444
|
+
Factories (return `Validator<TForm, TField>`): `required`, `email`, `min`, `max`, `minLength`, `maxLength`,
|
|
445
|
+
`pattern`, `url`, `phone`, `number`, `date`, `notEmpty`.
|
|
430
446
|
|
|
431
447
|
```typescript
|
|
432
|
-
// 1. BEST:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
448
|
+
// 1. BEST: Pass built-in factories to validate()
|
|
449
|
+
validate(path.email, required());
|
|
450
|
+
validate(path.email, email());
|
|
451
|
+
validate(path.age, min(18));
|
|
452
|
+
validate(path.password, minLength(8));
|
|
453
|
+
validate(path.phone, pattern(/^\+7\d{10}$/));
|
|
438
454
|
|
|
439
|
-
// 2. GOOD:
|
|
440
|
-
validate(path.customField, (value,
|
|
441
|
-
// Custom logic that can't be expressed with built-in validators
|
|
455
|
+
// 2. GOOD: Custom validator when no factory fits. Signature: (value, control, root).
|
|
456
|
+
validate(path.customField, (value, control, root) => {
|
|
442
457
|
if (customCondition(value)) {
|
|
443
458
|
return { code: 'custom', message: 'Custom error' };
|
|
444
459
|
}
|
|
445
460
|
return null;
|
|
446
461
|
});
|
|
447
462
|
|
|
448
|
-
// 3. WRONG: Don't recreate built-in validators
|
|
463
|
+
// 3. WRONG: Don't recreate built-in validators inline
|
|
449
464
|
validate(path.email, (value) => {
|
|
450
465
|
if (!value) return { code: 'required', message: 'Required' }; // Use required() instead!
|
|
451
466
|
if (!value.includes('@')) return { code: 'email', message: 'Invalid' }; // Use email() instead!
|
|
@@ -532,6 +547,68 @@ const behavior: BehaviorSchemaFn<LoanForm> = (path) => {
|
|
|
532
547
|
|
|
533
548
|
Reference patterns: `complex-multy-step-form/schemas/credit-application-behavior.ts`.
|
|
534
549
|
|
|
550
|
+
### Extracting Nested Rules
|
|
551
|
+
|
|
552
|
+
When the body of `applyWhen` / `validate` grows beyond a few lines,
|
|
553
|
+
extract it to a **named top-level function** typed with one of the public types from
|
|
554
|
+
`@reformer/core`:
|
|
555
|
+
|
|
556
|
+
- `ValidationSchemaFn<TForm>` — sub-schema passed to `applyWhen` or `apply`.
|
|
557
|
+
- `Validator<TForm, TField>` / `AsyncValidator<TForm, TField>` — field validator passed to `validate` / `validateAsync`. Cross-field правила пишутся в той же сигнатуре — `(value, control, root) => …` — и навешиваются на конкретное поле-носитель ошибки.
|
|
558
|
+
|
|
559
|
+
This keeps the schema body flat (reads like a table of contents) and surfaces the
|
|
560
|
+
**intent** of each rule via a meaningful name.
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
import type { ValidationSchemaFn, Validator } from '@reformer/core';
|
|
564
|
+
|
|
565
|
+
// 1. Cross-field rule — extracted Validator (reads other fields via `root`)
|
|
566
|
+
const initialPaymentVsPropertyValue: Validator<CreditApplicationForm, unknown> = (
|
|
567
|
+
_value,
|
|
568
|
+
_control,
|
|
569
|
+
root
|
|
570
|
+
) => {
|
|
571
|
+
const form = root.getValue();
|
|
572
|
+
if (form.initialPayment && form.propertyValue && form.initialPayment > form.propertyValue) {
|
|
573
|
+
return { code: 'initialPaymentTooHigh', message: 'Взнос не может превышать стоимость' };
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// 2. Custom field validator — extracted Validator
|
|
579
|
+
const validateAdultAge: Validator<CreditApplicationForm, string | undefined> = (value) => {
|
|
580
|
+
if (!value) return null;
|
|
581
|
+
const age = new Date().getFullYear() - new Date(value).getFullYear();
|
|
582
|
+
if (age < 18) return { code: 'tooYoung', message: 'Минимум 18 лет' };
|
|
583
|
+
return null;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// 3. Nested schema for applyWhen — extracted ValidationSchemaFn
|
|
587
|
+
const mortgageFieldsRules: ValidationSchemaFn<CreditApplicationForm> = (path) => {
|
|
588
|
+
validate(path.propertyValue, required());
|
|
589
|
+
validate(path.propertyValue, min(1000000));
|
|
590
|
+
validate(path.initialPayment, required());
|
|
591
|
+
validate(path.initialPayment, initialPaymentVsPropertyValue);
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// 4. Main schema — flat, reads as a table of contents
|
|
595
|
+
export const basicInfoValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
|
|
596
|
+
validate(path.loanType, required());
|
|
597
|
+
validate(path.personalData.birthDate, validateAdultAge);
|
|
598
|
+
applyWhen(path.loanType, (type) => type === 'mortgage', mortgageFieldsRules);
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Naming convention** (camelCase, semantic — not echoing the operator name):
|
|
603
|
+
|
|
604
|
+
- `applyWhen` sub-schema → describes the conditional branch: `mortgageFieldsRules`, `employedFieldsRules`.
|
|
605
|
+
- Cross-field `Validator` → describes the invariant: `initialPaymentVsPropertyValue`, `paymentToIncomeUnderHalf`.
|
|
606
|
+
- Field-level `Validator` → describes the check: `validateAdultAge`, `validatePasswordsMatch`.
|
|
607
|
+
|
|
608
|
+
**When to extract.** Inline lambdas are fine for short one-liners
|
|
609
|
+
(`applyWhen(path.x, (v) => v === 'mortgage', mortgageFieldsRules)` — the condition stays inline).
|
|
610
|
+
Extract whenever the body spans more than ~3 lines or contains nested rules.
|
|
611
|
+
|
|
535
612
|
## 6. 4. COMMON MISTAKES
|
|
536
613
|
|
|
537
614
|
### TSC overload-resolution error: `'form' does not exist in FormSchema<T>`
|
|
@@ -1274,19 +1351,32 @@ enableWhen(path.vehicle.vin, (form) => form.insuranceType === 'casco', { resetOn
|
|
|
1274
1351
|
|
|
1275
1352
|
See `22-cycle-detection.md` for complete pattern.
|
|
1276
1353
|
|
|
1277
|
-
###
|
|
1354
|
+
### Cross-field валидация — через `validate` + `root`
|
|
1278
1355
|
|
|
1279
1356
|
```typescript
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1357
|
+
// Validator signature: (value, control, root) => ValidationError | null
|
|
1358
|
+
// Cross-field правило вешается на ПОЛЕ-НОСИТЕЛЬ ошибки. Соседние поля читаются через `root`.
|
|
1282
1359
|
|
|
1283
|
-
// CORRECT
|
|
1284
|
-
|
|
1285
|
-
|
|
1360
|
+
// CORRECT — ошибка ложится на path.field1
|
|
1361
|
+
validate(path.field1, (_value, _control, root) => {
|
|
1362
|
+
const form = root.getValue(); // typed as TForm
|
|
1363
|
+
if (form.field1 > form.field2) {
|
|
1286
1364
|
return { code: 'error', message: 'Invalid' };
|
|
1287
1365
|
}
|
|
1288
1366
|
return null;
|
|
1289
1367
|
});
|
|
1368
|
+
|
|
1369
|
+
// CORRECT — те же поля через reactive-доступ
|
|
1370
|
+
validate(path.address.city, (value, _control, root) => {
|
|
1371
|
+
if (!value && root.address.region.value.value) {
|
|
1372
|
+
return { code: 'cityRequired', message: 'City required when region is set' };
|
|
1373
|
+
}
|
|
1374
|
+
return null;
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
// WRONG - old API removed
|
|
1378
|
+
validateGroup(scopePath, (scope, root) => { ... }); // validateGroup убран
|
|
1379
|
+
validateTree((ctx) => { ... }); // validateTree no longer exists
|
|
1290
1380
|
```
|
|
1291
1381
|
|
|
1292
1382
|
## 16. 14. PROJECT STRUCTURE (COLOCATION)
|
|
@@ -1787,18 +1877,18 @@ The "one watcher per trigger" cycle-prevention rule means **never register two w
|
|
|
1787
1877
|
|
|
1788
1878
|
```typescript
|
|
1789
1879
|
// WRONG - bracket notation does NOT work!
|
|
1790
|
-
const first = form.items[0];
|
|
1791
|
-
const second = form.items[1];
|
|
1880
|
+
const first = form.items[0]; // undefined or error
|
|
1881
|
+
const second = form.items[1]; // undefined or error
|
|
1792
1882
|
|
|
1793
1883
|
// CORRECT - use .at() method
|
|
1794
|
-
const first = form.items.at(0);
|
|
1795
|
-
const second = form.items.at(1);
|
|
1884
|
+
const first = form.items.at(0); // FormProxy<ItemType> | undefined
|
|
1885
|
+
const second = form.items.at(1); // FormProxy<ItemType> | undefined
|
|
1796
1886
|
|
|
1797
1887
|
// CORRECT - iterate with map (most common pattern)
|
|
1798
1888
|
form.items.map((item, index) => {
|
|
1799
1889
|
// item is fully typed GroupNode
|
|
1800
1890
|
item.name.setValue('New Name');
|
|
1801
|
-
item.price.value.value;
|
|
1891
|
+
item.price.value.value; // read value
|
|
1802
1892
|
});
|
|
1803
1893
|
```
|
|
1804
1894
|
|
|
@@ -1841,7 +1931,9 @@ function ItemsList({ form }: { form: FormProxy<MyForm> }) {
|
|
|
1841
1931
|
|
|
1842
1932
|
{length === 0 && <p>No items yet</p>}
|
|
1843
1933
|
|
|
1844
|
-
<button onClick={() => form.items.push({ name: '', price: 0 })}>
|
|
1934
|
+
<button onClick={() => form.items.push({ name: '', price: 0 })}>
|
|
1935
|
+
Add Item
|
|
1936
|
+
</button>
|
|
1845
1937
|
</div>
|
|
1846
1938
|
);
|
|
1847
1939
|
}
|
|
@@ -1849,35 +1941,35 @@ function ItemsList({ form }: { form: FormProxy<MyForm> }) {
|
|
|
1849
1941
|
|
|
1850
1942
|
### Array Cross-Validation
|
|
1851
1943
|
|
|
1944
|
+
Cross-field правила по массиву пишутся как обычный `Validator`, вешаются на сам массив
|
|
1945
|
+
(или на любое поле-носитель ошибки), а данные читаются через `root`.
|
|
1946
|
+
|
|
1852
1947
|
```typescript
|
|
1853
1948
|
// Validate uniqueness across array items
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
},
|
|
1865
|
-
{ targetField: 'items' }
|
|
1866
|
-
);
|
|
1949
|
+
validate(path.items, (_value, _control, root) => {
|
|
1950
|
+
const items = root.items;
|
|
1951
|
+
const names = items.map((item) => item.name.value.value);
|
|
1952
|
+
const uniqueNames = new Set(names);
|
|
1953
|
+
|
|
1954
|
+
if (names.length !== uniqueNames.size) {
|
|
1955
|
+
return { code: 'duplicate', message: 'Item names must be unique' };
|
|
1956
|
+
}
|
|
1957
|
+
return null;
|
|
1958
|
+
});
|
|
1867
1959
|
|
|
1868
1960
|
// Validate sum of percentages
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1961
|
+
validate(path.items, (_value, _control, root) => {
|
|
1962
|
+
const items = root.items;
|
|
1963
|
+
const totalPercent = items.reduce(
|
|
1964
|
+
(sum, item) => sum + (item.percentage.value.value || 0),
|
|
1965
|
+
0
|
|
1966
|
+
);
|
|
1873
1967
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
{ targetField: 'items' }
|
|
1880
|
-
);
|
|
1968
|
+
if (Math.abs(totalPercent - 100) > 0.01) {
|
|
1969
|
+
return { code: 'invalid_total', message: 'Percentages must sum to 100%' };
|
|
1970
|
+
}
|
|
1971
|
+
return null;
|
|
1972
|
+
});
|
|
1881
1973
|
```
|
|
1882
1974
|
|
|
1883
1975
|
## 22. Cycle Detection Prevention Checklist
|
|
@@ -3954,106 +4046,6 @@ export type AnyFunction = (...args: never[]) => unknown;
|
|
|
3954
4046
|
|
|
3955
4047
|
_Source: src/core/types/index.ts_
|
|
3956
4048
|
|
|
3957
|
-
### apply
|
|
3958
|
-
|
|
3959
|
-
**Kind:** `function`
|
|
3960
|
-
|
|
3961
|
-
Применить validation схему к вложенному полю или полям
|
|
3962
|
-
|
|
3963
|
-
Поддерживает:
|
|
3964
|
-
- Одно поле или массив полей
|
|
3965
|
-
- Одну схему или массив схем
|
|
3966
|
-
- Все комбинации (поле + схема, поле + схемы, поля + схема, поля + схемы)
|
|
3967
|
-
|
|
3968
|
-
**Signature:**
|
|
3969
|
-
```typescript
|
|
3970
|
-
export function apply<TForm, TField>(
|
|
3971
|
-
fields:
|
|
3972
|
-
| FieldPathNode<TForm, TField>
|
|
3973
|
-
| Array<FieldPathNode<TForm, TField> | undefined>
|
|
3974
|
-
| undefined,
|
|
3975
|
-
validationSchemas: ValidationSchemaFn<TField> | Array<ValidationSchemaFn<TField>>
|
|
3976
|
-
): void;
|
|
3977
|
-
```
|
|
3978
|
-
|
|
3979
|
-
**Parameters:**
|
|
3980
|
-
- `fields` — - Одно поле или массив полей
|
|
3981
|
-
- `validationSchemas` — - Одна схема или массив схем
|
|
3982
|
-
|
|
3983
|
-
**Examples:**
|
|
3984
|
-
|
|
3985
|
-
```typescript
|
|
3986
|
-
// Одна схема к одному полю
|
|
3987
|
-
apply(path.registrationAddress, addressValidation);
|
|
3988
|
-
|
|
3989
|
-
// Одна схема к нескольким полям
|
|
3990
|
-
apply([path.homeAddress, path.workAddress], addressValidation);
|
|
3991
|
-
|
|
3992
|
-
// Несколько схем к одному полю
|
|
3993
|
-
apply(path.email, [emailValidation, uniqueEmailValidation]);
|
|
3994
|
-
|
|
3995
|
-
// Несколько схем к нескольким полям
|
|
3996
|
-
apply(
|
|
3997
|
-
[path.email, path.confirmEmail],
|
|
3998
|
-
[emailValidation, matchValidation]
|
|
3999
|
-
);
|
|
4000
|
-
```
|
|
4001
|
-
|
|
4002
|
-
_Source: src/core/validation/core/apply.ts_
|
|
4003
|
-
|
|
4004
|
-
### applyWhen
|
|
4005
|
-
|
|
4006
|
-
**Kind:** `function`
|
|
4007
|
-
|
|
4008
|
-
Условное применение behavior схем (аналог applyWhen из validation API)
|
|
4009
|
-
|
|
4010
|
-
⚠️ ВАЖНО: Эта функция НЕ создаёт новые behaviors при каждом изменении условия!
|
|
4011
|
-
Вместо этого behaviors регистрируются ОДИН РАЗ при первом вызове и затем
|
|
4012
|
-
просто не выполняются, если условие не выполнено.
|
|
4013
|
-
|
|
4014
|
-
Это отличается от старой реализации, которая создавала утечку памяти,
|
|
4015
|
-
регистрируя behaviors при каждом изменении conditionField.
|
|
4016
|
-
|
|
4017
|
-
**Signature:**
|
|
4018
|
-
```typescript
|
|
4019
|
-
export function applyWhen<TForm extends FormFields, TValue extends FormValue>(
|
|
4020
|
-
conditionField: FieldPathNode<TForm, TValue> | undefined,
|
|
4021
|
-
condition: (value: TValue) => boolean,
|
|
4022
|
-
callback: (path: FieldPath<TForm>) => void
|
|
4023
|
-
): void
|
|
4024
|
-
```
|
|
4025
|
-
|
|
4026
|
-
**Parameters:**
|
|
4027
|
-
- `conditionField` — - Поле для проверки условия
|
|
4028
|
-
- `condition` — - Функция проверки условия
|
|
4029
|
-
- `callback` — - Callback для применения behavior схем
|
|
4030
|
-
|
|
4031
|
-
**Examples:**
|
|
4032
|
-
|
|
4033
|
-
```typescript
|
|
4034
|
-
// Применить addressBehavior только когда sameAsRegistration === false
|
|
4035
|
-
applyWhen(
|
|
4036
|
-
path.sameAsRegistration,
|
|
4037
|
-
(value) => value === false,
|
|
4038
|
-
(path) => {
|
|
4039
|
-
apply(path.residenceAddress, addressBehavior);
|
|
4040
|
-
}
|
|
4041
|
-
);
|
|
4042
|
-
|
|
4043
|
-
// Или с прямым использованием path
|
|
4044
|
-
applyWhen(
|
|
4045
|
-
path.hasProperty,
|
|
4046
|
-
(value) => value === true,
|
|
4047
|
-
(path) => {
|
|
4048
|
-
apply(path.properties, propertyBehavior);
|
|
4049
|
-
// Можно применить несколько схем
|
|
4050
|
-
apply([path.properties, path.items], arrayBehavior);
|
|
4051
|
-
}
|
|
4052
|
-
);
|
|
4053
|
-
```
|
|
4054
|
-
|
|
4055
|
-
_Source: src/core/behavior/compose-behavior.ts_
|
|
4056
|
-
|
|
4057
4049
|
### ArrayConfig
|
|
4058
4050
|
|
|
4059
4051
|
**Kind:** `interface`
|
|
@@ -4227,7 +4219,7 @@ ArrayNode - массив форм с реактивным состоянием
|
|
|
4227
4219
|
|
|
4228
4220
|
**Signature:**
|
|
4229
4221
|
```typescript
|
|
4230
|
-
export class ArrayNode<T extends
|
|
4222
|
+
export class ArrayNode<T extends object> extends FormNode<T[]> {
|
|
4231
4223
|
// ============================================================================
|
|
4232
4224
|
// Приватные поля
|
|
4233
4225
|
// ============================================================================ /* … */ }
|
|
@@ -4265,6 +4257,25 @@ export interface ArrayNodeLike {
|
|
|
4265
4257
|
|
|
4266
4258
|
_Source: src/core/types/index.ts_
|
|
4267
4259
|
|
|
4260
|
+
### AsyncValidator
|
|
4261
|
+
|
|
4262
|
+
**Kind:** `type`
|
|
4263
|
+
|
|
4264
|
+
Чистый асинхронный валидатор поля.
|
|
4265
|
+
|
|
4266
|
+
Регистрируется через `validateAsync(path, validator, { debounce })`.
|
|
4267
|
+
|
|
4268
|
+
**Signature:**
|
|
4269
|
+
```typescript
|
|
4270
|
+
export type AsyncValidator<TForm, TField> = (
|
|
4271
|
+
value: TField,
|
|
4272
|
+
control: FormProxy<TField>,
|
|
4273
|
+
root: FormProxy<TForm>
|
|
4274
|
+
) => Promise<ValidationError | null>;
|
|
4275
|
+
```
|
|
4276
|
+
|
|
4277
|
+
_Source: src/core/types/validation-schema.ts_
|
|
4278
|
+
|
|
4268
4279
|
### AsyncValidatorFn
|
|
4269
4280
|
|
|
4270
4281
|
**Kind:** `type`
|
|
@@ -4324,697 +4335,258 @@ export interface AsyncValidatorOptions {
|
|
|
4324
4335
|
|
|
4325
4336
|
_Source: src/core/types/index.ts_
|
|
4326
4337
|
|
|
4327
|
-
###
|
|
4338
|
+
### BehaviorCleanup
|
|
4328
4339
|
|
|
4329
4340
|
**Kind:** `type`
|
|
4330
4341
|
|
|
4331
|
-
|
|
4332
|
-
Алиас для FormContext
|
|
4342
|
+
Функция отписки от behavior-эффекта.
|
|
4333
4343
|
|
|
4334
4344
|
**Signature:**
|
|
4335
4345
|
```typescript
|
|
4336
|
-
export type
|
|
4337
|
-
```
|
|
4338
|
-
|
|
4339
|
-
**Examples:**
|
|
4340
|
-
|
|
4341
|
-
```typescript
|
|
4342
|
-
watchField(path.country, async (country, ctx) => {
|
|
4343
|
-
// Прямой типизированный доступ к полям
|
|
4344
|
-
const cities = await fetchCities(country);
|
|
4345
|
-
ctx.form.city.updateComponentProps({ options: cities });
|
|
4346
|
-
|
|
4347
|
-
// Безопасная установка значения (без циклов)
|
|
4348
|
-
ctx.setFieldValue('city', null);
|
|
4349
|
-
});
|
|
4346
|
+
export type BehaviorCleanup = () => void;
|
|
4350
4347
|
```
|
|
4351
4348
|
|
|
4352
|
-
_Source: src/core/
|
|
4349
|
+
_Source: src/core/model/behaviors.ts_
|
|
4353
4350
|
|
|
4354
|
-
###
|
|
4351
|
+
### computeFrom
|
|
4355
4352
|
|
|
4356
|
-
**Kind:** `
|
|
4353
|
+
**Kind:** `function`
|
|
4357
4354
|
|
|
4358
|
-
|
|
4359
|
-
в callback'и behaviors (`watchField`, `computeFrom`, …) — напрямую инстанцировать
|
|
4360
|
-
не нужно.
|
|
4355
|
+
Вычисляемое поле: `target = fn(...sourceValues)` при изменении источников.
|
|
4361
4356
|
|
|
4362
4357
|
**Signature:**
|
|
4363
4358
|
```typescript
|
|
4364
|
-
export
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4359
|
+
export function computeFrom<R>(
|
|
4360
|
+
sources: ReadonlySignal<any>[],
|
|
4361
|
+
target: Signal<R>,
|
|
4362
|
+
fn: (...values: any[]) => R,
|
|
4363
|
+
options?: { when?: (...values: any[]) => boolean }
|
|
4364
|
+
): BehaviorCleanup
|
|
4368
4365
|
```
|
|
4369
4366
|
|
|
4367
|
+
**Parameters:**
|
|
4368
|
+
- `sources` — Сигналы-источники (`model.$.a`, `model.$.b`).
|
|
4369
|
+
- `target` — Сигнал-цель (`model.$.total`).
|
|
4370
|
+
- `fn` — Функция вычисления значения.
|
|
4371
|
+
|
|
4372
|
+
**Returns:** Cleanup для отписки.
|
|
4373
|
+
|
|
4370
4374
|
**Examples:**
|
|
4371
4375
|
|
|
4372
4376
|
```typescript
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
watchField(path.country, (value, ctx) => {
|
|
4376
|
-
// ctx — экземпляр BehaviorContextImpl
|
|
4377
|
-
ctx.setFieldValue(path.city, '');
|
|
4378
|
-
});
|
|
4377
|
+
computeFrom([model.$.price, model.$.qty], model.$.total, (price, qty) => price * qty);
|
|
4379
4378
|
```
|
|
4380
4379
|
|
|
4381
|
-
_Source: src/core/
|
|
4380
|
+
_Source: src/core/model/behaviors.ts_
|
|
4382
4381
|
|
|
4383
|
-
###
|
|
4382
|
+
### ConditionFn
|
|
4384
4383
|
|
|
4385
4384
|
**Kind:** `type`
|
|
4386
4385
|
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
Создает effect подписку для реактивного поведения формы.
|
|
4386
|
+
Функция условия для applyWhen.
|
|
4390
4387
|
|
|
4391
4388
|
**Signature:**
|
|
4392
4389
|
```typescript
|
|
4393
|
-
export type
|
|
4394
|
-
form: GroupNode<TForm>,
|
|
4395
|
-
context: BehaviorContext<TForm>,
|
|
4396
|
-
withDebounce: (callback: () => void) => void
|
|
4397
|
-
) => (() => void) | null;
|
|
4390
|
+
export type ConditionFn<T> = (value: T) => boolean;
|
|
4398
4391
|
```
|
|
4399
4392
|
|
|
4400
|
-
|
|
4401
|
-
- `form` — - Корневой узел формы (GroupNode)
|
|
4402
|
-
- `context` — - Контекст для работы с формой
|
|
4403
|
-
- `withDebounce` — - Функция-обертка для debounce
|
|
4393
|
+
_Source: src/core/types/validation-schema.ts_
|
|
4404
4394
|
|
|
4405
|
-
|
|
4395
|
+
### ConfigWithSchema
|
|
4406
4396
|
|
|
4407
|
-
**
|
|
4397
|
+
**Kind:** `interface`
|
|
4408
4398
|
|
|
4409
|
-
|
|
4410
|
-
const handler: BehaviorHandlerFn<MyForm> = (form, context, withDebounce) => {
|
|
4411
|
-
const sourceNode = form.getFieldByPath('email');
|
|
4399
|
+
Конфиг с полем schema (для ArrayConfig)
|
|
4412
4400
|
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
};
|
|
4401
|
+
**Signature:**
|
|
4402
|
+
```typescript
|
|
4403
|
+
export interface ConfigWithSchema {
|
|
4404
|
+
schema: unknown;
|
|
4405
|
+
initialItems?: unknown[];
|
|
4406
|
+
}
|
|
4420
4407
|
```
|
|
4421
4408
|
|
|
4422
|
-
_Source: src/core/
|
|
4409
|
+
_Source: src/core/types/index.ts_
|
|
4423
4410
|
|
|
4424
|
-
###
|
|
4411
|
+
### ConfigWithValue
|
|
4425
4412
|
|
|
4426
4413
|
**Kind:** `interface`
|
|
4427
4414
|
|
|
4428
|
-
|
|
4415
|
+
Конфиг с полем value (для извлечения значений)
|
|
4429
4416
|
|
|
4430
4417
|
**Signature:**
|
|
4431
4418
|
```typescript
|
|
4432
|
-
export interface
|
|
4433
|
-
|
|
4434
|
-
debounce?: number;
|
|
4419
|
+
export interface ConfigWithValue {
|
|
4420
|
+
value: unknown;
|
|
4435
4421
|
}
|
|
4436
4422
|
```
|
|
4437
4423
|
|
|
4438
|
-
_Source: src/core/
|
|
4439
|
-
|
|
4440
|
-
### BehaviorRegistry
|
|
4441
|
-
|
|
4442
|
-
**Kind:** `class`
|
|
4424
|
+
_Source: src/core/types/index.ts_
|
|
4443
4425
|
|
|
4444
|
-
|
|
4426
|
+
### copyFrom
|
|
4445
4427
|
|
|
4446
|
-
|
|
4447
|
-
Устраняет race conditions и изолирует формы друг от друга.
|
|
4428
|
+
**Kind:** `function`
|
|
4448
4429
|
|
|
4449
|
-
|
|
4450
|
-
- Управления global stack
|
|
4451
|
-
- Template methods begin/end registration
|
|
4430
|
+
Копирование значения `source → target` (опционально по условию/с трансформом).
|
|
4452
4431
|
|
|
4453
4432
|
**Signature:**
|
|
4454
4433
|
```typescript
|
|
4455
|
-
export
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
*
|
|
4461
|
-
* @example
|
|
4462
|
-
* ```typescript
|
|
4463
|
-
* // В schema-behaviors.ts
|
|
4464
|
-
* export function copyFrom(...) {
|
|
4465
|
-
* const registry = BehaviorRegistry.getCurrent();
|
|
4466
|
-
* if (registry) {
|
|
4467
|
-
* registry.register({ ... });
|
|
4468
|
-
* }
|
|
4469
|
-
* }
|
|
4470
|
-
* ```
|
|
4471
|
-
*/ /* … */ }
|
|
4434
|
+
export function copyFrom<T>(
|
|
4435
|
+
source: ReadonlySignal<T>,
|
|
4436
|
+
target: Signal<T>,
|
|
4437
|
+
options?: { when?: () => boolean; transform?: (value: T) => T }
|
|
4438
|
+
): BehaviorCleanup
|
|
4472
4439
|
```
|
|
4473
4440
|
|
|
4474
4441
|
**Examples:**
|
|
4475
4442
|
|
|
4476
4443
|
```typescript
|
|
4477
|
-
|
|
4478
|
-
private readonly behaviorRegistry = new BehaviorRegistry();
|
|
4479
|
-
|
|
4480
|
-
applyBehaviorSchema(schemaFn) {
|
|
4481
|
-
this.behaviorRegistry.beginRegistration(); // Pushes this to stack
|
|
4482
|
-
schemaFn(createBehaviorFieldPath(this)); // Uses getCurrent()
|
|
4483
|
-
return this.behaviorRegistry.endRegistration(this); // Pops from stack
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
```
|
|
4487
|
-
|
|
4488
|
-
_Source: src/core/behavior/behavior-registry.ts_
|
|
4489
|
-
|
|
4490
|
-
### BehaviorSchemaFn
|
|
4491
|
-
|
|
4492
|
-
**Kind:** `type`
|
|
4493
|
-
|
|
4494
|
-
Тип функции behavior схемы
|
|
4495
|
-
Принимает FieldPath и описывает поведение формы
|
|
4496
|
-
|
|
4497
|
-
**Signature:**
|
|
4498
|
-
```typescript
|
|
4499
|
-
export type BehaviorSchemaFn<T> = (path: FieldPath<T>) => void;
|
|
4444
|
+
copyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail });
|
|
4500
4445
|
```
|
|
4501
4446
|
|
|
4502
|
-
_Source: src/core/
|
|
4447
|
+
_Source: src/core/model/behaviors.ts_
|
|
4503
4448
|
|
|
4504
|
-
###
|
|
4449
|
+
### createForm
|
|
4505
4450
|
|
|
4506
4451
|
**Kind:** `function`
|
|
4507
4452
|
|
|
4508
|
-
|
|
4453
|
+
Создать форму с полной конфигурацией (form, behavior, validation)
|
|
4509
4454
|
|
|
4510
4455
|
**Signature:**
|
|
4511
4456
|
```typescript
|
|
4512
|
-
export function
|
|
4513
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4514
|
-
sources: FieldPathNode<TForm, any>[],
|
|
4515
|
-
target: FieldPathNode<TForm, TTarget>,
|
|
4516
|
-
computeFn: (values: TForm) => TTarget,
|
|
4517
|
-
options?: ComputeFromOptions<TForm>
|
|
4518
|
-
): void
|
|
4457
|
+
export function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
|
|
4519
4458
|
```
|
|
4520
4459
|
|
|
4521
4460
|
**Parameters:**
|
|
4522
|
-
- `
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
- `options` — - Опции (`debounce`, `condition`, `trigger`)
|
|
4461
|
+
- `config` — - Конфигурация формы с полями, поведением и валидацией
|
|
4462
|
+
|
|
4463
|
+
**Returns:** Типизированная форма с Proxy-доступом к полям
|
|
4526
4464
|
|
|
4527
4465
|
**Examples:**
|
|
4528
4466
|
|
|
4529
|
-
Многополевой расчёт — total = price × quantity
|
|
4530
4467
|
```typescript
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4468
|
+
const form = createForm<UserForm>({
|
|
4469
|
+
form: {
|
|
4470
|
+
email: { value: '', component: Input },
|
|
4471
|
+
password: { value: '', component: Input },
|
|
4472
|
+
},
|
|
4473
|
+
validation: (path) => {
|
|
4474
|
+
required(path.email);
|
|
4475
|
+
email(path.email);
|
|
4476
|
+
required(path.password);
|
|
4477
|
+
minLength(path.password, 8);
|
|
4478
|
+
},
|
|
4479
|
+
});
|
|
4538
4480
|
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
[path.price, path.quantity],
|
|
4542
|
-
path.total,
|
|
4543
|
-
(values) =>
|
|
4544
|
-
(typeof values.price === 'number' ? values.price : 0) *
|
|
4545
|
-
(typeof values.quantity === 'number' ? values.quantity : 0),
|
|
4546
|
-
);
|
|
4547
|
-
};
|
|
4481
|
+
// TypeScript знает о полях:
|
|
4482
|
+
form.email.setValue('test@mail.com');
|
|
4548
4483
|
```
|
|
4549
4484
|
|
|
4550
|
-
|
|
4551
|
-
```typescript
|
|
4552
|
-
import { computeFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4485
|
+
_Source: src/core/utils/create-form.ts_
|
|
4553
4486
|
|
|
4554
|
-
|
|
4555
|
-
loanType: 'mortgage' | 'consumer';
|
|
4556
|
-
loanAmount: number;
|
|
4557
|
-
loanTerm: number;
|
|
4558
|
-
interestRate: number;
|
|
4559
|
-
monthlyPayment: number;
|
|
4560
|
-
}
|
|
4487
|
+
### createFormFromModel
|
|
4561
4488
|
|
|
4562
|
-
function
|
|
4563
|
-
const { loanAmount, loanTerm, interestRate } = values;
|
|
4564
|
-
if (!loanAmount || !loanTerm || !interestRate) return 0;
|
|
4565
|
-
const r = interestRate / 100 / 12;
|
|
4566
|
-
const n = loanTerm;
|
|
4567
|
-
return Math.round((loanAmount * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1));
|
|
4568
|
-
}
|
|
4489
|
+
**Kind:** `function`
|
|
4569
4490
|
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
path.monthlyPayment,
|
|
4574
|
-
annuity,
|
|
4575
|
-
{
|
|
4576
|
-
debounce: 300, // не пересчитываем на каждый keystroke
|
|
4577
|
-
condition: (form) => form.loanType === 'mortgage', // считаем только для ипотеки
|
|
4578
|
-
},
|
|
4579
|
-
);
|
|
4580
|
-
};
|
|
4491
|
+
**Signature:**
|
|
4492
|
+
```typescript
|
|
4493
|
+
export function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>
|
|
4581
4494
|
```
|
|
4582
4495
|
|
|
4583
|
-
|
|
4584
|
-
- [docs/llms/20-compute-vs-watch.md](../../../../docs/llms/20-compute-vs-watch.md)
|
|
4585
|
-
|
|
4586
|
-
_Source: src/core/behavior/behaviors/compute-from.ts_
|
|
4496
|
+
_Source: src/core/utils/create-form.ts_
|
|
4587
4497
|
|
|
4588
|
-
###
|
|
4498
|
+
### CreateFormFromModelArgs
|
|
4589
4499
|
|
|
4590
4500
|
**Kind:** `interface`
|
|
4591
4501
|
|
|
4592
|
-
|
|
4502
|
+
Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
|
|
4503
|
+
конфиг полей (component/componentProps/validators) — из единой схемы.
|
|
4593
4504
|
|
|
4594
4505
|
**Signature:**
|
|
4595
4506
|
```typescript
|
|
4596
|
-
export interface
|
|
4597
|
-
/**
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4507
|
+
export interface CreateFormFromModelArgs<T> {
|
|
4508
|
+
/** Реактивная модель данных (источник истины значений). */
|
|
4509
|
+
model: FormModel<T>;
|
|
4510
|
+
/**
|
|
4511
|
+
* Единая Schema (дерево узлов). createForm обходит её и привязывает конфиг поля к ноде
|
|
4512
|
+
* по идентичности сигнала (`node.value === model.$.path`). Опциональна.
|
|
4513
|
+
*/
|
|
4514
|
+
schema?: unknown;
|
|
4515
|
+
/**
|
|
4516
|
+
* Декларативная схема поведения ({@link defineFormBehavior}). Запускается ПОСЛЕ построения нод и
|
|
4517
|
+
* заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
|
|
4518
|
+
*/
|
|
4519
|
+
behavior?: FormBehavior<T>;
|
|
4605
4520
|
}
|
|
4606
4521
|
```
|
|
4607
4522
|
|
|
4608
|
-
_Source: src/core/
|
|
4523
|
+
_Source: src/core/utils/create-form.ts_
|
|
4609
4524
|
|
|
4610
|
-
###
|
|
4525
|
+
### createModel
|
|
4611
4526
|
|
|
4612
|
-
**Kind:** `
|
|
4527
|
+
**Kind:** `function`
|
|
4613
4528
|
|
|
4614
|
-
|
|
4529
|
+
Создать реактивную модель данных формы (слой M1).
|
|
4615
4530
|
|
|
4616
4531
|
**Signature:**
|
|
4617
4532
|
```typescript
|
|
4618
|
-
export
|
|
4533
|
+
export function createModel<T extends object>(initial: T): FormModel<T>
|
|
4619
4534
|
```
|
|
4620
4535
|
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
### ConfigWithSchema
|
|
4536
|
+
**Parameters:**
|
|
4537
|
+
- `initial` — Начальные значения (объект). Определяют форму данных и initial-снимок.
|
|
4624
4538
|
|
|
4625
|
-
**
|
|
4539
|
+
**Returns:**
|
|
4626
4540
|
|
|
4627
|
-
|
|
4541
|
+
**Examples:**
|
|
4628
4542
|
|
|
4629
|
-
**Signature:**
|
|
4630
4543
|
```typescript
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4544
|
+
const model = createModel<{ email: string; tags: string[] }>({ email: '', tags: [] });
|
|
4545
|
+
model.email = 'a@b.c';
|
|
4546
|
+
model.$.email.value; // 'a@b.c' (сигнал)
|
|
4547
|
+
model.tags.push('x');
|
|
4548
|
+
model.get(); // { email: 'a@b.c', tags: ['x'] }
|
|
4635
4549
|
```
|
|
4636
4550
|
|
|
4637
|
-
_Source: src/core/
|
|
4551
|
+
_Source: src/core/model/form-model.ts_
|
|
4638
4552
|
|
|
4639
|
-
###
|
|
4553
|
+
### disableWhen
|
|
4640
4554
|
|
|
4641
|
-
**Kind:** `
|
|
4555
|
+
**Kind:** `function`
|
|
4642
4556
|
|
|
4643
|
-
|
|
4557
|
+
Условное выключение поля (инверсия {@link enableWhen}).
|
|
4644
4558
|
|
|
4645
4559
|
**Signature:**
|
|
4646
4560
|
```typescript
|
|
4647
|
-
export
|
|
4648
|
-
|
|
4649
|
-
|
|
4561
|
+
export function disableWhen(
|
|
4562
|
+
target: ReadonlySignal<unknown>,
|
|
4563
|
+
condition: () => boolean,
|
|
4564
|
+
options?: { resetOnDisable?: boolean }
|
|
4565
|
+
): BehaviorCleanup
|
|
4650
4566
|
```
|
|
4651
4567
|
|
|
4652
|
-
_Source: src/core/
|
|
4568
|
+
_Source: src/core/model/behaviors.ts_
|
|
4653
4569
|
|
|
4654
|
-
###
|
|
4655
|
-
|
|
4656
|
-
**Kind:** `type`
|
|
4657
|
-
|
|
4658
|
-
Асинхронная функция валидации поля с контекстом
|
|
4659
|
-
|
|
4660
|
-
**Signature:**
|
|
4661
|
-
```typescript
|
|
4662
|
-
export type ContextualAsyncValidatorFn<TForm, TField> = (
|
|
4663
|
-
value: TField,
|
|
4664
|
-
ctx: FormContext<TForm>
|
|
4665
|
-
) => Promise<ValidationError | null>;
|
|
4666
|
-
```
|
|
4667
|
-
|
|
4668
|
-
**Examples:**
|
|
4669
|
-
|
|
4670
|
-
```typescript
|
|
4671
|
-
validateAsync(path.email, async (value, ctx) => {
|
|
4672
|
-
const exists = await checkEmailExists(value);
|
|
4673
|
-
if (exists) return { code: 'exists', message: 'Email already taken' };
|
|
4674
|
-
return null;
|
|
4675
|
-
});
|
|
4676
|
-
```
|
|
4677
|
-
|
|
4678
|
-
_Source: src/core/types/validation-schema.ts_
|
|
4679
|
-
|
|
4680
|
-
### ContextualValidatorFn
|
|
4681
|
-
|
|
4682
|
-
**Kind:** `type`
|
|
4683
|
-
|
|
4684
|
-
Функция валидации поля с контекстом
|
|
4685
|
-
|
|
4686
|
-
Новый паттерн: (value, ctx: FormContext) => ValidationError | null
|
|
4687
|
-
|
|
4688
|
-
**Signature:**
|
|
4689
|
-
```typescript
|
|
4690
|
-
export type ContextualValidatorFn<TForm, TField> = (
|
|
4691
|
-
value: TField,
|
|
4692
|
-
ctx: FormContext<TForm>
|
|
4693
|
-
) => ValidationError | null;
|
|
4694
|
-
```
|
|
4695
|
-
|
|
4696
|
-
**Examples:**
|
|
4697
|
-
|
|
4698
|
-
```typescript
|
|
4699
|
-
validate(path.email, (value, ctx) => {
|
|
4700
|
-
if (!value) return { code: 'required', message: 'Email required' };
|
|
4701
|
-
const confirm = ctx.form.confirmEmail.value.value;
|
|
4702
|
-
if (value !== confirm) return { code: 'mismatch', message: 'Must match' };
|
|
4703
|
-
return null;
|
|
4704
|
-
});
|
|
4705
|
-
```
|
|
4706
|
-
|
|
4707
|
-
_Source: src/core/types/validation-schema.ts_
|
|
4708
|
-
|
|
4709
|
-
### copyFrom
|
|
4710
|
-
|
|
4711
|
-
**Kind:** `function`
|
|
4712
|
-
|
|
4713
|
-
Копирует значения из одного поля/группы в другое при выполнении условия
|
|
4714
|
-
|
|
4715
|
-
**Signature:**
|
|
4716
|
-
```typescript
|
|
4717
|
-
export function copyFrom<TForm, TSource, TTarget>(
|
|
4718
|
-
source: FieldPathNode<TForm, TSource>,
|
|
4719
|
-
target: FieldPathNode<TForm, TTarget>,
|
|
4720
|
-
options?: CopyFromOptions<TSource, TForm>
|
|
4721
|
-
): void
|
|
4722
|
-
```
|
|
4723
|
-
|
|
4724
|
-
**Parameters:**
|
|
4725
|
-
- `source` — - Откуда копировать
|
|
4726
|
-
- `target` — - Куда копировать
|
|
4727
|
-
- `options` — - Опции копирования (`when`, `fields`, `transform`, `debounce`)
|
|
4728
|
-
|
|
4729
|
-
**Examples:**
|
|
4730
|
-
|
|
4731
|
-
Скаляр → скаляр с условием
|
|
4732
|
-
```typescript
|
|
4733
|
-
import { copyFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4734
|
-
|
|
4735
|
-
interface ContactForm {
|
|
4736
|
-
sameEmail: boolean;
|
|
4737
|
-
email: string;
|
|
4738
|
-
emailAdditional: string;
|
|
4739
|
-
}
|
|
4740
|
-
|
|
4741
|
-
export const contactBehavior: BehaviorSchemaFn<ContactForm> = (path) => {
|
|
4742
|
-
copyFrom(path.email, path.emailAdditional, {
|
|
4743
|
-
when: (form) => form.sameEmail === true,
|
|
4744
|
-
});
|
|
4745
|
-
};
|
|
4746
|
-
```
|
|
4747
|
-
|
|
4748
|
-
Копирование группы с подмножеством полей и `transform`
|
|
4749
|
-
```typescript
|
|
4750
|
-
import { copyFrom, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4751
|
-
|
|
4752
|
-
interface Address { country: string; region: string; city: string; street: string }
|
|
4753
|
-
interface ProfileForm {
|
|
4754
|
-
sameAsRegistration: boolean;
|
|
4755
|
-
registrationAddress: Address;
|
|
4756
|
-
residenceAddress: Address;
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
4760
|
-
copyFrom(path.registrationAddress, path.residenceAddress, {
|
|
4761
|
-
when: (form) => form.sameAsRegistration === true,
|
|
4762
|
-
fields: ['country', 'region', 'city'], // street НЕ копируем
|
|
4763
|
-
transform: (addr) => ({
|
|
4764
|
-
...addr,
|
|
4765
|
-
country: addr.country.toUpperCase(), // нормализация при копировании
|
|
4766
|
-
}),
|
|
4767
|
-
debounce: 200,
|
|
4768
|
-
});
|
|
4769
|
-
};
|
|
4770
|
-
```
|
|
4771
|
-
|
|
4772
|
-
**See also:**
|
|
4773
|
-
- [docs/llms/23-copy-from.md](../../../../docs/llms/23-copy-from.md)
|
|
4774
|
-
|
|
4775
|
-
_Source: src/core/behavior/behaviors/copy-from.ts_
|
|
4776
|
-
|
|
4777
|
-
### CopyFromOptions
|
|
4778
|
-
|
|
4779
|
-
**Kind:** `interface`
|
|
4780
|
-
|
|
4781
|
-
Опции для copyFrom
|
|
4782
|
-
|
|
4783
|
-
**Signature:**
|
|
4784
|
-
```typescript
|
|
4785
|
-
export interface CopyFromOptions<TSource, TForm = unknown> {
|
|
4786
|
-
/** Условие копирования */
|
|
4787
|
-
when?: (form: TForm) => boolean;
|
|
4788
|
-
|
|
4789
|
-
/** Какие поля копировать (для групп) */
|
|
4790
|
-
fields?: (keyof TSource)[] | 'all';
|
|
4791
|
-
|
|
4792
|
-
/** Трансформация значения */
|
|
4793
|
-
transform?: (value: TSource) => unknown;
|
|
4794
|
-
|
|
4795
|
-
/** Debounce в мс */
|
|
4796
|
-
debounce?: number;
|
|
4797
|
-
}
|
|
4798
|
-
```
|
|
4799
|
-
|
|
4800
|
-
_Source: src/core/behavior/types.ts_
|
|
4801
|
-
|
|
4802
|
-
### createFieldPath
|
|
4803
|
-
|
|
4804
|
-
**Kind:** `function`
|
|
4805
|
-
|
|
4806
|
-
Создать FieldPath proxy для формы
|
|
4807
|
-
|
|
4808
|
-
**Signature:**
|
|
4809
|
-
```typescript
|
|
4810
|
-
export function createFieldPath<T>(): FieldPath<T>
|
|
4811
|
-
```
|
|
4812
|
-
|
|
4813
|
-
**Examples:**
|
|
4814
|
-
|
|
4815
|
-
```typescript
|
|
4816
|
-
const path = createFieldPath<MyForm>();
|
|
4817
|
-
console.log(path.email.__path); // 'email'
|
|
4818
|
-
console.log(path.personalData.firstName.__path); // 'personalData.firstName'
|
|
4819
|
-
```
|
|
4820
|
-
|
|
4821
|
-
_Source: src/core/utils/field-path.ts_
|
|
4822
|
-
|
|
4823
|
-
### createForm
|
|
4824
|
-
|
|
4825
|
-
**Kind:** `function`
|
|
4826
|
-
|
|
4827
|
-
Создать форму с полной конфигурацией (form, behavior, validation)
|
|
4828
|
-
|
|
4829
|
-
**Signature:**
|
|
4830
|
-
```typescript
|
|
4831
|
-
export function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
|
|
4832
|
-
```
|
|
4833
|
-
|
|
4834
|
-
**Parameters:**
|
|
4835
|
-
- `config` — - Конфигурация формы с полями, поведением и валидацией
|
|
4836
|
-
|
|
4837
|
-
**Returns:** Типизированная форма с Proxy-доступом к полям
|
|
4838
|
-
|
|
4839
|
-
**Examples:**
|
|
4840
|
-
|
|
4841
|
-
```typescript
|
|
4842
|
-
const form = createForm<UserForm>({
|
|
4843
|
-
form: {
|
|
4844
|
-
email: { value: '', component: Input },
|
|
4845
|
-
password: { value: '', component: Input },
|
|
4846
|
-
},
|
|
4847
|
-
validation: (path) => {
|
|
4848
|
-
required(path.email);
|
|
4849
|
-
email(path.email);
|
|
4850
|
-
required(path.password);
|
|
4851
|
-
minLength(path.password, 8);
|
|
4852
|
-
},
|
|
4853
|
-
});
|
|
4854
|
-
|
|
4855
|
-
// TypeScript знает о полях:
|
|
4856
|
-
form.email.setValue('test@mail.com');
|
|
4857
|
-
```
|
|
4858
|
-
|
|
4859
|
-
_Source: src/core/utils/create-form.ts_
|
|
4860
|
-
|
|
4861
|
-
### createTransformer
|
|
4570
|
+
### email
|
|
4862
4571
|
|
|
4863
4572
|
**Kind:** `function`
|
|
4864
4573
|
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
**Signature:**
|
|
4868
|
-
```typescript
|
|
4869
|
-
export function createTransformer<TValue extends FormValue = FormValue>(
|
|
4870
|
-
transformer: (value: TValue) => TValue,
|
|
4871
|
-
defaultOptions?: TransformValueOptions
|
|
4872
|
-
)
|
|
4873
|
-
```
|
|
4874
|
-
|
|
4875
|
-
**Parameters:**
|
|
4876
|
-
- `transformer` — - Идемпотентная функция преобразования значения
|
|
4877
|
-
- `defaultOptions` — - Опции, применяемые ко всем вызовам созданного трансформера
|
|
4878
|
-
|
|
4879
|
-
**Examples:**
|
|
4880
|
-
|
|
4881
|
-
Доменно-специфичные трансформеры (банковский счёт, СНИЛС)
|
|
4882
|
-
```typescript
|
|
4883
|
-
import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4884
|
-
|
|
4885
|
-
// Сохраняем только цифры и форматируем СНИЛС: 000-000-000 00
|
|
4886
|
-
const formatSnils = createTransformer<string>((v) => {
|
|
4887
|
-
const d = (v ?? '').replace(/\D/g, '').slice(0, 11);
|
|
4888
|
-
if (d.length < 9) return d;
|
|
4889
|
-
return `${d.slice(0, 3)}-${d.slice(3, 6)}-${d.slice(6, 9)}${d.length > 9 ? ' ' + d.slice(9) : ''}`;
|
|
4890
|
-
});
|
|
4891
|
-
|
|
4892
|
-
interface ProfileForm { snils: string }
|
|
4893
|
-
|
|
4894
|
-
export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
4895
|
-
formatSnils(path.snils, { debounce: 100 });
|
|
4896
|
-
};
|
|
4897
|
-
```
|
|
4898
|
-
|
|
4899
|
-
С `defaultOptions` — единые настройки на серию полей
|
|
4900
|
-
```typescript
|
|
4901
|
-
import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4902
|
-
|
|
4903
|
-
// Все коды должны быть uppercase, но только после правки пользователем
|
|
4904
|
-
const upperOnUserEdit = createTransformer<string>(
|
|
4905
|
-
(v) => (v ?? '').toUpperCase(),
|
|
4906
|
-
{ onUserChangeOnly: true, debounce: 100 },
|
|
4907
|
-
);
|
|
4908
|
-
|
|
4909
|
-
interface PromoForm { promoCode: string; partnerCode: string }
|
|
4910
|
-
|
|
4911
|
-
export const promoBehavior: BehaviorSchemaFn<PromoForm> = (path) => {
|
|
4912
|
-
upperOnUserEdit(path.promoCode);
|
|
4913
|
-
upperOnUserEdit(path.partnerCode);
|
|
4914
|
-
};
|
|
4915
|
-
```
|
|
4916
|
-
|
|
4917
|
-
_Source: src/core/behavior/behaviors/transform-value.ts_
|
|
4918
|
-
|
|
4919
|
-
### disableWhen
|
|
4920
|
-
|
|
4921
|
-
**Kind:** `function`
|
|
4922
|
-
|
|
4923
|
-
Условное выключение поля (инверсия enableWhen)
|
|
4924
|
-
|
|
4925
|
-
**Signature:**
|
|
4926
|
-
```typescript
|
|
4927
|
-
export function disableWhen<TForm>(
|
|
4928
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4929
|
-
field: FieldPathNode<TForm, any>,
|
|
4930
|
-
condition: (form: TForm) => boolean,
|
|
4931
|
-
options?: EnableWhenOptions
|
|
4932
|
-
): void
|
|
4933
|
-
```
|
|
4934
|
-
|
|
4935
|
-
**Parameters:**
|
|
4936
|
-
- `field` — - Поле для выключения
|
|
4937
|
-
- `condition` — - Функция условия (true = disable, false = enable)
|
|
4938
|
-
- `options` — - Опции (`resetOnDisable`, `debounce`)
|
|
4574
|
+
Фабрика валидатора формата email.
|
|
4939
4575
|
|
|
4940
|
-
**Examples:**
|
|
4941
|
-
|
|
4942
|
-
Базовый сценарий — readonly после подтверждения
|
|
4943
|
-
```typescript
|
|
4944
|
-
import { disableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4945
|
-
|
|
4946
|
-
interface ConfirmForm {
|
|
4947
|
-
isConfirmed: boolean;
|
|
4948
|
-
editableField: string;
|
|
4949
|
-
}
|
|
4950
|
-
|
|
4951
|
-
export const confirmBehavior: BehaviorSchemaFn<ConfirmForm> = (path) => {
|
|
4952
|
-
// Поле блокируется после установки чекбокса подтверждения
|
|
4953
|
-
disableWhen(path.editableField, (form) => form.isConfirmed === true);
|
|
4954
|
-
// resetOnDisable НЕ ставим — сохраняем введённый текст
|
|
4955
|
-
};
|
|
4956
|
-
```
|
|
4957
|
-
|
|
4958
|
-
С `resetOnDisable` для очистки заблокированного поля
|
|
4959
|
-
```typescript
|
|
4960
|
-
import { disableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
4961
|
-
|
|
4962
|
-
interface PromoForm {
|
|
4963
|
-
loanType: 'mortgage' | 'consumer';
|
|
4964
|
-
promoCode: string;
|
|
4965
|
-
}
|
|
4966
|
-
|
|
4967
|
-
export const promoBehavior: BehaviorSchemaFn<PromoForm> = (path) => {
|
|
4968
|
-
// Промокод недоступен для потребительских кредитов и сбрасывается
|
|
4969
|
-
disableWhen(path.promoCode, (form) => form.loanType === 'consumer', {
|
|
4970
|
-
resetOnDisable: true,
|
|
4971
|
-
});
|
|
4972
|
-
};
|
|
4973
|
-
```
|
|
4974
|
-
|
|
4975
|
-
_Source: src/core/behavior/behaviors/enable-when.ts_
|
|
4976
|
-
|
|
4977
|
-
### email
|
|
4978
|
-
|
|
4979
|
-
**Kind:** `function`
|
|
4980
|
-
|
|
4981
|
-
Валидатор формата email
|
|
4982
|
-
|
|
4983
|
-
Проверяет, что значение соответствует формату email адреса.
|
|
4984
4576
|
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
4985
4577
|
|
|
4986
4578
|
**Signature:**
|
|
4987
4579
|
```typescript
|
|
4988
|
-
export function email<TForm, TField extends string |
|
|
4989
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
4580
|
+
export function email<TForm = unknown, TField extends string | undefined = string>(
|
|
4990
4581
|
options?: ValidateOptions
|
|
4991
|
-
):
|
|
4582
|
+
): Validator<TForm, TField>
|
|
4992
4583
|
```
|
|
4993
4584
|
|
|
4994
|
-
**Parameters:**
|
|
4995
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
4996
|
-
- `options` — - Опции валидации (message, params)
|
|
4997
|
-
|
|
4998
4585
|
**Examples:**
|
|
4999
4586
|
|
|
5000
4587
|
```typescript
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
required(path.email),
|
|
5004
|
-
email(path.email),
|
|
5005
|
-
]
|
|
5006
|
-
|
|
5007
|
-
// С кастомным сообщением
|
|
5008
|
-
email(path.email, { message: 'Введите корректный email адрес' })
|
|
5009
|
-
```
|
|
5010
|
-
|
|
5011
|
-
```typescript
|
|
5012
|
-
// Ошибка валидации
|
|
5013
|
-
{
|
|
5014
|
-
code: 'email',
|
|
5015
|
-
message: 'Неверный формат email',
|
|
5016
|
-
params: {}
|
|
5017
|
-
}
|
|
4588
|
+
validate(path.email, email());
|
|
4589
|
+
validate(path.email, email({ message: 'Введите корректный email' }));
|
|
5018
4590
|
```
|
|
5019
4591
|
|
|
5020
4592
|
_Source: src/core/validation/validators/email.ts_
|
|
@@ -5023,107 +4595,30 @@ _Source: src/core/validation/validators/email.ts_
|
|
|
5023
4595
|
|
|
5024
4596
|
**Kind:** `function`
|
|
5025
4597
|
|
|
5026
|
-
Условное включение поля
|
|
4598
|
+
Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
|
|
4599
|
+
сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
|
|
4600
|
+
`resetOnDisable`). `condition` реактивен (читает свои сигналы модели).
|
|
4601
|
+
|
|
4602
|
+
Запись состояния отложена через `runOutsideEffect` (микротаск) для защиты от «Cycle detected».
|
|
4603
|
+
⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
|
|
4604
|
+
элемент массива, который строится per-item).
|
|
5027
4605
|
|
|
5028
4606
|
**Signature:**
|
|
5029
4607
|
```typescript
|
|
5030
|
-
export function enableWhen
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
): void
|
|
4608
|
+
export function enableWhen(
|
|
4609
|
+
target: ReadonlySignal<unknown>,
|
|
4610
|
+
condition: () => boolean,
|
|
4611
|
+
options?: { resetOnDisable?: boolean }
|
|
4612
|
+
): BehaviorCleanup
|
|
5036
4613
|
```
|
|
5037
4614
|
|
|
5038
|
-
**Parameters:**
|
|
5039
|
-
- `field` — - Поле для включения/выключения
|
|
5040
|
-
- `condition` — - Функция условия (true = enable, false = disable)
|
|
5041
|
-
- `options` — - Опции (`resetOnDisable`, `debounce`)
|
|
5042
|
-
|
|
5043
4615
|
**Examples:**
|
|
5044
4616
|
|
|
5045
|
-
Базовый сценарий с `resetOnDisable: true`
|
|
5046
4617
|
```typescript
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
interface LoanForm {
|
|
5050
|
-
loanType: 'mortgage' | 'consumer' | 'car';
|
|
5051
|
-
propertyValue: number;
|
|
5052
|
-
initialPayment: number;
|
|
5053
|
-
}
|
|
5054
|
-
|
|
5055
|
-
export const loanBehavior: BehaviorSchemaFn<LoanForm> = (path) => {
|
|
5056
|
-
// Поля ипотеки активны только для loanType === 'mortgage'.
|
|
5057
|
-
// resetOnDisable: true гарантирует чистые initial values при переключении.
|
|
5058
|
-
enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage', {
|
|
5059
|
-
resetOnDisable: true,
|
|
5060
|
-
});
|
|
5061
|
-
enableWhen(path.initialPayment, (form) => form.loanType === 'mortgage', {
|
|
5062
|
-
resetOnDisable: true,
|
|
5063
|
-
});
|
|
5064
|
-
};
|
|
4618
|
+
enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
|
|
5065
4619
|
```
|
|
5066
4620
|
|
|
5067
|
-
|
|
5068
|
-
```typescript
|
|
5069
|
-
import { enableWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
5070
|
-
|
|
5071
|
-
interface ProfileForm {
|
|
5072
|
-
sameAsRegistration: boolean;
|
|
5073
|
-
employmentStatus: 'employed' | 'selfEmployed' | 'unemployed';
|
|
5074
|
-
residenceAddress: { city: string; street: string };
|
|
5075
|
-
companyName: string;
|
|
5076
|
-
companyInn: string;
|
|
5077
|
-
businessType: string;
|
|
5078
|
-
}
|
|
5079
|
-
|
|
5080
|
-
export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
5081
|
-
// Адрес проживания: enabled, когда НЕ совпадает с регистрационным
|
|
5082
|
-
enableWhen(path.residenceAddress, (form) => form.sameAsRegistration === false, {
|
|
5083
|
-
resetOnDisable: true,
|
|
5084
|
-
});
|
|
5085
|
-
|
|
5086
|
-
// Поля работодателя: только для employed
|
|
5087
|
-
enableWhen(path.companyName, (form) => form.employmentStatus === 'employed', {
|
|
5088
|
-
resetOnDisable: true,
|
|
5089
|
-
});
|
|
5090
|
-
enableWhen(path.companyInn, (form) => form.employmentStatus === 'employed', {
|
|
5091
|
-
resetOnDisable: true,
|
|
5092
|
-
});
|
|
5093
|
-
|
|
5094
|
-
// ИП-поля: только для selfEmployed
|
|
5095
|
-
enableWhen(path.businessType, (form) => form.employmentStatus === 'selfEmployed', {
|
|
5096
|
-
resetOnDisable: true,
|
|
5097
|
-
});
|
|
5098
|
-
|
|
5099
|
-
// ВАЖНО: condition не должен читать значение САМОГО поля — иначе цикл.
|
|
5100
|
-
// condition зависит ТОЛЬКО от независимых триггеров (loanType, employmentStatus, ...).
|
|
5101
|
-
};
|
|
5102
|
-
```
|
|
5103
|
-
|
|
5104
|
-
**See also:**
|
|
5105
|
-
- [docs/llms/22-cycle-detection.md](../../../../docs/llms/22-cycle-detection.md)
|
|
5106
|
-
|
|
5107
|
-
_Source: src/core/behavior/behaviors/enable-when.ts_
|
|
5108
|
-
|
|
5109
|
-
### EnableWhenOptions
|
|
5110
|
-
|
|
5111
|
-
**Kind:** `interface`
|
|
5112
|
-
|
|
5113
|
-
Опции для enableWhen/disableWhen
|
|
5114
|
-
|
|
5115
|
-
**Signature:**
|
|
5116
|
-
```typescript
|
|
5117
|
-
export interface EnableWhenOptions {
|
|
5118
|
-
/** Сбросить значение при disable */
|
|
5119
|
-
resetOnDisable?: boolean;
|
|
5120
|
-
|
|
5121
|
-
/** Debounce в мс */
|
|
5122
|
-
debounce?: number;
|
|
5123
|
-
}
|
|
5124
|
-
```
|
|
5125
|
-
|
|
5126
|
-
_Source: src/core/behavior/types.ts_
|
|
4621
|
+
_Source: src/core/model/behaviors.ts_
|
|
5127
4622
|
|
|
5128
4623
|
### ErrorFilterOptions
|
|
5129
4624
|
|
|
@@ -5183,58 +4678,6 @@ export enum ErrorStrategy {
|
|
|
5183
4678
|
|
|
5184
4679
|
_Source: src/core/utils/error-handler.ts_
|
|
5185
4680
|
|
|
5186
|
-
### extractKey
|
|
5187
|
-
|
|
5188
|
-
**Kind:** `function`
|
|
5189
|
-
|
|
5190
|
-
Извлечь имя последнего сегмента ({@link FieldPathSegment}) пути.
|
|
5191
|
-
|
|
5192
|
-
**Signature:**
|
|
5193
|
-
```typescript
|
|
5194
|
-
export function extractKey(node: FieldPathNode<unknown, unknown> | unknown): string
|
|
5195
|
-
```
|
|
5196
|
-
|
|
5197
|
-
**Parameters:**
|
|
5198
|
-
- `node` — - Узел `FieldPathNode` либо строка-путь.
|
|
5199
|
-
|
|
5200
|
-
**Returns:** Имя поля без префикса родителя (последний сегмент).
|
|
5201
|
-
|
|
5202
|
-
**Examples:**
|
|
5203
|
-
|
|
5204
|
-
```typescript
|
|
5205
|
-
import { extractKey } from '@reformer/core';
|
|
5206
|
-
|
|
5207
|
-
extractKey(path.user.email); // → 'email'
|
|
5208
|
-
```
|
|
5209
|
-
|
|
5210
|
-
_Source: src/core/utils/field-path.ts_
|
|
5211
|
-
|
|
5212
|
-
### extractPath
|
|
5213
|
-
|
|
5214
|
-
**Kind:** `function`
|
|
5215
|
-
|
|
5216
|
-
Извлечь строковый путь из {@link FieldPathNode}.
|
|
5217
|
-
|
|
5218
|
-
**Signature:**
|
|
5219
|
-
```typescript
|
|
5220
|
-
export function extractPath(node: FieldPathNode<unknown, unknown> | unknown): string
|
|
5221
|
-
```
|
|
5222
|
-
|
|
5223
|
-
**Parameters:**
|
|
5224
|
-
- `node` — - Узел `FieldPathNode` либо строка-путь.
|
|
5225
|
-
|
|
5226
|
-
**Returns:** Путь вида `"a.b.c"`.
|
|
5227
|
-
|
|
5228
|
-
**Examples:**
|
|
5229
|
-
|
|
5230
|
-
```typescript
|
|
5231
|
-
import { extractPath } from '@reformer/core';
|
|
5232
|
-
|
|
5233
|
-
const path = (renderPath) => extractPath(renderPath.user.email); // → 'user.email'
|
|
5234
|
-
```
|
|
5235
|
-
|
|
5236
|
-
_Source: src/core/utils/field-path.ts_
|
|
5237
|
-
|
|
5238
4681
|
### FieldConfig
|
|
5239
4682
|
|
|
5240
4683
|
**Kind:** `interface`
|
|
@@ -5244,9 +4687,22 @@ _Source: src/core/utils/field-path.ts_
|
|
|
5244
4687
|
**Signature:**
|
|
5245
4688
|
```typescript
|
|
5246
4689
|
export interface FieldConfig<T> {
|
|
5247
|
-
|
|
4690
|
+
/**
|
|
4691
|
+
* Начальное значение-литерал (legacy-путь). Под архитектурой M1 значение приходит из
|
|
4692
|
+
* {@link FieldConfig.valueSignal} (сигнал {@link FormModel}); тогда `value` не требуется.
|
|
4693
|
+
*/
|
|
4694
|
+
value?: T | null;
|
|
4695
|
+
/**
|
|
4696
|
+
* Сигнал значения из {@link FormModel} (M1). Если задан — служит источником истины значения
|
|
4697
|
+
* поля (нода не владеет значением, а ссылается на этот сигнал). Имеет приоритет над `value`.
|
|
4698
|
+
*/
|
|
4699
|
+
valueSignal?: Signal<T>;
|
|
4700
|
+
/**
|
|
4701
|
+
* UI-компонент поля. Опционален: core-часть можно использовать без ссылки на компонент
|
|
4702
|
+
* (значение/валидация работают без UI; компонент нужен только для рендеринга).
|
|
4703
|
+
*/
|
|
5248
4704
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5249
|
-
component
|
|
4705
|
+
component?: ComponentType<any>;
|
|
5250
4706
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5251
4707
|
componentProps?: any;
|
|
5252
4708
|
validators?: ValidatorFn<T>[];
|
|
@@ -5500,133 +4956,6 @@ console.log(field.valid.value); // true
|
|
|
5500
4956
|
|
|
5501
4957
|
_Source: src/core/nodes/field-node.ts_
|
|
5502
4958
|
|
|
5503
|
-
### FieldPath
|
|
5504
|
-
|
|
5505
|
-
**Kind:** `type`
|
|
5506
|
-
|
|
5507
|
-
FieldPath предоставляет типобезопасный доступ к путям полей формы
|
|
5508
|
-
|
|
5509
|
-
Рекурсивно обрабатывает вложенные объекты для поддержки вложенных форм.
|
|
5510
|
-
|
|
5511
|
-
Использование:
|
|
5512
|
-
```typescript
|
|
5513
|
-
const validation = (path: FieldPath<MyForm>) => {
|
|
5514
|
-
required(path.email, { message: 'Email обязателен' });
|
|
5515
|
-
|
|
5516
|
-
// Вложенные объекты
|
|
5517
|
-
required(path.registrationAddress.city);
|
|
5518
|
-
minLength(path.registrationAddress.street, 3);
|
|
5519
|
-
|
|
5520
|
-
applyWhen(
|
|
5521
|
-
path.loanType,
|
|
5522
|
-
(type) => type === 'mortgage',
|
|
5523
|
-
(path) => {
|
|
5524
|
-
required(path.propertyValue, { message: 'Укажите стоимость' });
|
|
5525
|
-
}
|
|
5526
|
-
);
|
|
5527
|
-
};
|
|
5528
|
-
```
|
|
5529
|
-
|
|
5530
|
-
**Signature:**
|
|
5531
|
-
```typescript
|
|
5532
|
-
export type FieldPath<T> = {
|
|
5533
|
-
[K in keyof T]: NonNullable<T[K]> extends Array<unknown>
|
|
5534
|
-
? FieldPathNode<T, T[K], K> // Массивы - не рекурсим (обрабатываются отдельно)
|
|
5535
|
-
: NonNullable<T[K]> extends Date | File | Blob | AnyFunction
|
|
5536
|
-
? FieldPathNode<T, T[K], K> // Специальные объекты - не рекурсим
|
|
5537
|
-
: NonNullable<T[K]> extends object
|
|
5538
|
-
? FieldPathNode<T, T[K], K> & FieldPath<NonNullable<T[K]>> // Обычные объекты - рекурсия!
|
|
5539
|
-
: FieldPathNode<T, T[K], K>; // Примитивы
|
|
5540
|
-
};
|
|
5541
|
-
```
|
|
5542
|
-
|
|
5543
|
-
_Source: src/core/types/field-path.ts_
|
|
5544
|
-
|
|
5545
|
-
### FieldPathNavigator
|
|
5546
|
-
|
|
5547
|
-
**Kind:** `class`
|
|
5548
|
-
|
|
5549
|
-
Навигация по путям к полям формы
|
|
5550
|
-
|
|
5551
|
-
Централизует логику парсинга и навигации по путям к полям формы.
|
|
5552
|
-
Используется в ValidationContext, BehaviorContext, GroupNode для единообразной
|
|
5553
|
-
обработки путей вида "address.city" или "items[0].name".
|
|
5554
|
-
|
|
5555
|
-
Устраняет дублирование логики парсинга путей в 4 местах кодовой базы.
|
|
5556
|
-
|
|
5557
|
-
**Signature:**
|
|
5558
|
-
```typescript
|
|
5559
|
-
export class FieldPathNavigator {
|
|
5560
|
-
/**
|
|
5561
|
-
* Парсит путь в массив сегментов
|
|
5562
|
-
*
|
|
5563
|
-
* Поддерживаемые форматы:
|
|
5564
|
-
* - Простые пути: "name", "email"
|
|
5565
|
-
* - Вложенные пути: "address.city", "user.profile.avatar"
|
|
5566
|
-
* - Массивы: "items[0]", "items[0].name", "tags[1][0]"
|
|
5567
|
-
* - Комбинации: "orders[0].items[1].price"
|
|
5568
|
-
*
|
|
5569
|
-
* @param path Путь к полю (строка с точками и квадратными скобками)
|
|
5570
|
-
* @returns Массив сегментов пути
|
|
5571
|
-
*
|
|
5572
|
-
* @example
|
|
5573
|
-
* ```typescript
|
|
5574
|
-
* navigator.parsePath('email');
|
|
5575
|
-
* // [{ key: 'email' }]
|
|
5576
|
-
*
|
|
5577
|
-
* navigator.parsePath('address.city');
|
|
5578
|
-
* // [{ key: 'address' }, { key: 'city' }]
|
|
5579
|
-
*
|
|
5580
|
-
* navigator.parsePath('items[0].name');
|
|
5581
|
-
* // [{ key: 'items', index: 0 }, { key: 'name' }]
|
|
5582
|
-
* ```
|
|
5583
|
-
*/ /* … */ }
|
|
5584
|
-
```
|
|
5585
|
-
|
|
5586
|
-
**Examples:**
|
|
5587
|
-
|
|
5588
|
-
```typescript
|
|
5589
|
-
const navigator = new FieldPathNavigator();
|
|
5590
|
-
|
|
5591
|
-
// Парсинг пути
|
|
5592
|
-
const segments = navigator.parsePath('items[0].title');
|
|
5593
|
-
// [{ key: 'items', index: 0 }, { key: 'title' }]
|
|
5594
|
-
|
|
5595
|
-
// Получение значения из объекта
|
|
5596
|
-
const obj = { items: [{ title: 'Item 1' }] };
|
|
5597
|
-
const value = navigator.getValueByPath(obj, 'items[0].title');
|
|
5598
|
-
// 'Item 1'
|
|
5599
|
-
|
|
5600
|
-
// Получение узла формы
|
|
5601
|
-
const titleNode = navigator.getNodeByPath(form, 'items[0].title');
|
|
5602
|
-
titleNode?.setValue('New Title');
|
|
5603
|
-
```
|
|
5604
|
-
|
|
5605
|
-
_Source: src/core/utils/field-path-navigator.ts_
|
|
5606
|
-
|
|
5607
|
-
### FieldPathNode
|
|
5608
|
-
|
|
5609
|
-
**Kind:** `interface`
|
|
5610
|
-
|
|
5611
|
-
Узел в пути поля
|
|
5612
|
-
Содержит метаинформацию о поле для валидации
|
|
5613
|
-
|
|
5614
|
-
**Signature:**
|
|
5615
|
-
```typescript
|
|
5616
|
-
export interface FieldPathNode<TForm, TField, TKey = unknown> {
|
|
5617
|
-
/** Ключ поля */
|
|
5618
|
-
readonly __key: TKey;
|
|
5619
|
-
/** Путь к полю (для вложенных объектов) */
|
|
5620
|
-
readonly __path: string;
|
|
5621
|
-
/** Тип формы */
|
|
5622
|
-
readonly __formType?: TForm;
|
|
5623
|
-
/** Тип поля */
|
|
5624
|
-
readonly __fieldType?: TField;
|
|
5625
|
-
}
|
|
5626
|
-
```
|
|
5627
|
-
|
|
5628
|
-
_Source: src/core/types/field-path.ts_
|
|
5629
|
-
|
|
5630
4959
|
### FieldPathSegment
|
|
5631
4960
|
|
|
5632
4961
|
**Kind:** `type`
|
|
@@ -5667,7 +4996,7 @@ _Source: src/core/types/index.ts_
|
|
|
5667
4996
|
|
|
5668
4997
|
**Signature:**
|
|
5669
4998
|
```typescript
|
|
5670
|
-
export type FormArrayProxy<T extends
|
|
4999
|
+
export type FormArrayProxy<T extends object> = ArrayNode<T> & {
|
|
5671
5000
|
/**
|
|
5672
5001
|
* Безопасный доступ к элементу массива по индексу
|
|
5673
5002
|
* Возвращает GroupNode с типизированными полями или undefined
|
|
@@ -5761,102 +5090,6 @@ export type FormChangeType = 'value' | 'status' | 'errors' | 'touched' | 'dirty'
|
|
|
5761
5090
|
|
|
5762
5091
|
_Source: src/core/utils/form-observer.ts_
|
|
5763
5092
|
|
|
5764
|
-
### FormContext
|
|
5765
|
-
|
|
5766
|
-
**Kind:** `interface`
|
|
5767
|
-
|
|
5768
|
-
Единый контекст для работы с формой
|
|
5769
|
-
|
|
5770
|
-
Предоставляет:
|
|
5771
|
-
- `form` - типизированный Proxy-доступ к полям формы
|
|
5772
|
-
- `setFieldValue` - безопасная установка значения (emitEvent: false)
|
|
5773
|
-
|
|
5774
|
-
**Signature:**
|
|
5775
|
-
```typescript
|
|
5776
|
-
export interface FormContext<TForm> {
|
|
5777
|
-
/**
|
|
5778
|
-
* Форма с типизированным Proxy-доступом к полям
|
|
5779
|
-
*
|
|
5780
|
-
* Позволяет обращаться к полям напрямую через точечную нотацию:
|
|
5781
|
-
* - `ctx.form.email` → FieldNode
|
|
5782
|
-
* - `ctx.form.address.city` → FieldNode (вложенный)
|
|
5783
|
-
* - `ctx.form.items` → ArrayNode
|
|
5784
|
-
*
|
|
5785
|
-
* @example
|
|
5786
|
-
* ```typescript
|
|
5787
|
-
* // Получить значение
|
|
5788
|
-
* ctx.form.email.value.value
|
|
5789
|
-
*
|
|
5790
|
-
* // Установить значение (⚠️ может вызвать цикл в behavior!)
|
|
5791
|
-
* ctx.form.email.setValue('new@mail.com')
|
|
5792
|
-
*
|
|
5793
|
-
* // Безопасно установить значение
|
|
5794
|
-
* ctx.form.email.setValue('new@mail.com', { emitEvent: false })
|
|
5795
|
-
*
|
|
5796
|
-
* // Обновить пропсы компонента
|
|
5797
|
-
* ctx.form.city.updateComponentProps({ options: cities })
|
|
5798
|
-
*
|
|
5799
|
-
* // Валидация поля
|
|
5800
|
-
* await ctx.form.email.validate()
|
|
5801
|
-
*
|
|
5802
|
-
* // Работа с массивами
|
|
5803
|
-
* ctx.form.items.push({ title: 'New' })
|
|
5804
|
-
* ctx.form.items.clear()
|
|
5805
|
-
* ```
|
|
5806
|
-
*/
|
|
5807
|
-
readonly form: FormProxy<TForm>;
|
|
5808
|
-
|
|
5809
|
-
/**
|
|
5810
|
-
* Безопасно установить значение поля по строковому пути или FieldPath
|
|
5811
|
-
*
|
|
5812
|
-
* Автоматически использует `emitEvent: false` для предотвращения
|
|
5813
|
-
* бесконечных циклов в behavior схемах.
|
|
5814
|
-
*
|
|
5815
|
-
* @param path - Путь к полю (строка или FieldPath)
|
|
5816
|
-
* @param value - Новое значение
|
|
5817
|
-
*
|
|
5818
|
-
* @example
|
|
5819
|
-
* ```typescript
|
|
5820
|
-
* // Сброс города при смене страны (строковый путь)
|
|
5821
|
-
* watchField(path.country, (country, ctx) => {
|
|
5822
|
-
* ctx.setFieldValue('city', null);
|
|
5823
|
-
* });
|
|
5824
|
-
*
|
|
5825
|
-
* // Использование FieldPath напрямую (более типобезопасно)
|
|
5826
|
-
* watchField(path.country, (country, ctx) => {
|
|
5827
|
-
* ctx.setFieldValue(path.city, null);
|
|
5828
|
-
* });
|
|
5829
|
-
* ```
|
|
5830
|
-
*/
|
|
5831
|
-
setFieldValue(path: string | FieldPathNode<TForm, unknown>, value: unknown): void;
|
|
5832
|
-
|
|
5833
|
-
/**
|
|
5834
|
-
* Получить поле формы по строковому пути
|
|
5835
|
-
*
|
|
5836
|
-
* Используется для динамического доступа к вложенным полям,
|
|
5837
|
-
* особенно в модульных behavior схемах, применяемых через apply().
|
|
5838
|
-
*
|
|
5839
|
-
* @param path - Строковый путь к полю (например "address.city")
|
|
5840
|
-
* @returns FormNode или undefined если поле не найдено
|
|
5841
|
-
*
|
|
5842
|
-
* @example
|
|
5843
|
-
* ```typescript
|
|
5844
|
-
* // В модульной behavior схеме, применяемой к вложенному полю:
|
|
5845
|
-
* // apply([path.registrationAddress, path.residenceAddress], addressBehavior)
|
|
5846
|
-
*
|
|
5847
|
-
* watchField(path.region, (region, ctx) => {
|
|
5848
|
-
* // path.city.__path = "registrationAddress.city" или "residenceAddress.city"
|
|
5849
|
-
* const cityField = ctx.getFieldByPath(path.city.__path);
|
|
5850
|
-
* cityField?.updateComponentProps({ options: cities });
|
|
5851
|
-
* });
|
|
5852
|
-
* ```
|
|
5853
|
-
*/
|
|
5854
|
-
getFieldByPath(path: string): FormNode<FormValue> | undefined;
|
|
5855
|
-
}
|
|
5856
|
-
```
|
|
5857
|
-
|
|
5858
|
-
_Source: src/core/types/form-context.ts_
|
|
5859
|
-
|
|
5860
5093
|
### FormControlsProxy
|
|
5861
5094
|
|
|
5862
5095
|
**Kind:** `type`
|
|
@@ -5874,15 +5107,16 @@ _Source: src/core/types/form-context.ts_
|
|
|
5874
5107
|
**Signature:**
|
|
5875
5108
|
```typescript
|
|
5876
5109
|
export type FormControlsProxy<T> = {
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5110
|
+
// `-?` снимает опциональность: для каждого поля схемы прокси всегда содержит узел
|
|
5111
|
+
// (включая опциональные поля — у них узел существует, опционально лишь значение).
|
|
5112
|
+
// Без этого `control.optionalField` имел бы тип `FieldNode<...> | undefined`.
|
|
5113
|
+
[K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<infer U>
|
|
5114
|
+
? IsGroupObject<U> extends true
|
|
5115
|
+
? FormArrayProxy<U & object> // Массив объектов → FormArrayProxy
|
|
5880
5116
|
: FieldNode<T[K]> // Массив примитивов → FieldNode
|
|
5881
|
-
: NonNullable<T[K]
|
|
5882
|
-
? NonNullable<T[K]
|
|
5883
|
-
|
|
5884
|
-
: FormProxy<NonNullable<T[K]>> // Обычный объект → FormProxy (рекурсивно!)
|
|
5885
|
-
: FieldNode<T[K]>; // Примитивные типы → FieldNode
|
|
5117
|
+
: IsGroupObject<NonNullable<T[K]>> extends true
|
|
5118
|
+
? FormProxy<NonNullable<T[K]>> // Обычный объект → FormProxy (рекурсивно!)
|
|
5119
|
+
: FieldNode<T[K]>; // Примитивы и спец-объекты (Date/File/Blob) → FieldNode
|
|
5886
5120
|
};
|
|
5887
5121
|
```
|
|
5888
5122
|
|
|
@@ -5986,6 +5220,19 @@ export type FormFields = Record<string, FormValue>;
|
|
|
5986
5220
|
|
|
5987
5221
|
_Source: src/core/types/index.ts_
|
|
5988
5222
|
|
|
5223
|
+
### FormModel
|
|
5224
|
+
|
|
5225
|
+
**Kind:** `type`
|
|
5226
|
+
|
|
5227
|
+
FormModel — value-proxy объекта `T` + {@link ModelApi}.
|
|
5228
|
+
|
|
5229
|
+
**Signature:**
|
|
5230
|
+
```typescript
|
|
5231
|
+
export type FormModel<T> = ModelObject<T> & ModelApi<T>;
|
|
5232
|
+
```
|
|
5233
|
+
|
|
5234
|
+
_Source: src/core/model/types.ts_
|
|
5235
|
+
|
|
5989
5236
|
### FormNode
|
|
5990
5237
|
|
|
5991
5238
|
**Kind:** `class`
|
|
@@ -6276,99 +5523,40 @@ _Source: src/core/types/index.ts_
|
|
|
6276
5523
|
|
|
6277
5524
|
**Kind:** `function`
|
|
6278
5525
|
|
|
6279
|
-
|
|
5526
|
+
Фабрика валидатора, проверяющего что дата не в прошлом.
|
|
6280
5527
|
|
|
6281
|
-
Пустые
|
|
5528
|
+
Пустые и невалидные даты пропускаются.
|
|
6282
5529
|
|
|
6283
5530
|
**Signature:**
|
|
6284
5531
|
```typescript
|
|
6285
|
-
export function futureDate<
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
):
|
|
5532
|
+
export function futureDate<
|
|
5533
|
+
TForm = unknown,
|
|
5534
|
+
TField extends string | Date | undefined = string | Date,
|
|
5535
|
+
>(options?: ValidateOptions): Validator<TForm, TField>
|
|
6289
5536
|
```
|
|
6290
5537
|
|
|
6291
|
-
**Parameters:**
|
|
6292
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6293
|
-
- `options` — - Опции валидации (message, params)
|
|
6294
|
-
|
|
6295
5538
|
**Examples:**
|
|
6296
5539
|
|
|
6297
5540
|
```typescript
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
futureDate(path.eventDate),
|
|
6301
|
-
]
|
|
6302
|
-
|
|
6303
|
-
// С кастомным сообщением
|
|
6304
|
-
futureDate(path.appointmentDate, { message: 'Дата записи должна быть в будущем' })
|
|
6305
|
-
```
|
|
6306
|
-
|
|
6307
|
-
```typescript
|
|
6308
|
-
// Ошибка валидации
|
|
6309
|
-
{
|
|
6310
|
-
code: 'date_past',
|
|
6311
|
-
message: 'Дата не может быть в прошлом',
|
|
6312
|
-
params: {}
|
|
6313
|
-
}
|
|
5541
|
+
validate(path.eventDate, futureDate());
|
|
5542
|
+
validate(path.appointmentDate, futureDate({ message: 'Дата записи должна быть в будущем' }));
|
|
6314
5543
|
```
|
|
6315
5544
|
|
|
6316
5545
|
_Source: src/core/validation/validators/future-date.ts_
|
|
6317
5546
|
|
|
6318
|
-
###
|
|
6319
|
-
|
|
6320
|
-
**Kind:** `function`
|
|
6321
|
-
|
|
6322
|
-
Получить текущий активный BehaviorRegistry из context stack
|
|
6323
|
-
|
|
6324
|
-
Используется внутри behavior schema функций (copyFrom, enableWhen, computeFrom и т.д.)
|
|
6325
|
-
для регистрации поведений в активном контексте GroupNode
|
|
6326
|
-
|
|
6327
|
-
**Signature:**
|
|
6328
|
-
```typescript
|
|
6329
|
-
export function getCurrentBehaviorRegistry(): BehaviorRegistry
|
|
6330
|
-
```
|
|
6331
|
-
|
|
6332
|
-
**Returns:** Текущий BehaviorRegistry или заглушка в production
|
|
6333
|
-
|
|
6334
|
-
**Examples:**
|
|
6335
|
-
|
|
6336
|
-
```typescript
|
|
6337
|
-
function copyFrom(target, source, options) {
|
|
6338
|
-
const registry = getCurrentBehaviorRegistry();
|
|
6339
|
-
const handler = createCopyBehavior(target, source, options);
|
|
6340
|
-
registry.register(handler, { debounce: options?.debounce });
|
|
6341
|
-
}
|
|
6342
|
-
```
|
|
6343
|
-
|
|
6344
|
-
_Source: src/core/utils/registry-helpers.ts_
|
|
6345
|
-
|
|
6346
|
-
### getCurrentValidationRegistry
|
|
5547
|
+
### getNodeForSignal
|
|
6347
5548
|
|
|
6348
5549
|
**Kind:** `function`
|
|
6349
5550
|
|
|
6350
|
-
Получить
|
|
6351
|
-
|
|
6352
|
-
Используется внутри validation schema функций (validate, validateAsync, required и т.д.)
|
|
6353
|
-
для регистрации валидаторов в активном контексте GroupNode
|
|
5551
|
+
Получить FieldNode по сигналу модели (или undefined, если форма не построена/поле не материализовано,
|
|
5552
|
+
например элемент массива в родительской форме).
|
|
6354
5553
|
|
|
6355
5554
|
**Signature:**
|
|
6356
5555
|
```typescript
|
|
6357
|
-
export function
|
|
5556
|
+
export function getNodeForSignal(signal: Signal<any>): FormNode<any> | undefined
|
|
6358
5557
|
```
|
|
6359
5558
|
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
**Examples:**
|
|
6363
|
-
|
|
6364
|
-
```typescript
|
|
6365
|
-
function required(fieldPath, options) {
|
|
6366
|
-
const registry = getCurrentValidationRegistry();
|
|
6367
|
-
registry.registerSync(path, validator, options);
|
|
6368
|
-
}
|
|
6369
|
-
```
|
|
6370
|
-
|
|
6371
|
-
_Source: src/core/utils/registry-helpers.ts_
|
|
5559
|
+
_Source: src/core/utils/signal-node-registry.ts_
|
|
6372
5560
|
|
|
6373
5561
|
### getNodeType
|
|
6374
5562
|
|
|
@@ -6402,9 +5590,9 @@ _Source: src/core/utils/type-guards.ts_
|
|
|
6402
5590
|
|
|
6403
5591
|
GroupNode - узел для группы полей
|
|
6404
5592
|
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
5593
|
+
Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
|
|
5594
|
+
(M1: `createForm({ model, schema })`); валидация/behavior живут на слое модели
|
|
5595
|
+
(`validateFormModel`, `computeFrom`/`enableWhen`/…), а не на ноде.
|
|
6408
5596
|
|
|
6409
5597
|
**Signature:**
|
|
6410
5598
|
```typescript
|
|
@@ -6417,33 +5605,16 @@ export class GroupNode<T> extends FormNode<T> {
|
|
|
6417
5605
|
**Examples:**
|
|
6418
5606
|
|
|
6419
5607
|
```typescript
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
password: { value: '', component: Input },
|
|
6424
|
-
});
|
|
6425
|
-
|
|
6426
|
-
// 2. Новый способ (с behavior и validation схемами)
|
|
6427
|
-
const fullForm = new GroupNode({
|
|
6428
|
-
form: {
|
|
6429
|
-
email: { value: '', component: Input },
|
|
6430
|
-
password: { value: '', component: Input },
|
|
6431
|
-
},
|
|
6432
|
-
behavior: (path) => {
|
|
6433
|
-
computeFrom(path.email, [path.email], (values) => values[0]?.trim());
|
|
6434
|
-
},
|
|
6435
|
-
validation: (path) => {
|
|
6436
|
-
required(path.email, { message: 'Email обязателен' });
|
|
6437
|
-
email(path.email);
|
|
6438
|
-
required(path.password);
|
|
6439
|
-
minLength(path.password, 8);
|
|
6440
|
-
},
|
|
5608
|
+
const form = new GroupNode({
|
|
5609
|
+
email: { valueSignal: model.$.email, component: Input },
|
|
5610
|
+
password: { valueSignal: model.$.password, component: Input },
|
|
6441
5611
|
});
|
|
6442
5612
|
|
|
6443
5613
|
// Прямой доступ к полям через Proxy
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
5614
|
+
const proxy = form.getProxy();
|
|
5615
|
+
proxy.email.setValue('test@mail.com');
|
|
5616
|
+
await proxy.validate();
|
|
5617
|
+
console.log(proxy.valid.value);
|
|
6447
5618
|
```
|
|
6448
5619
|
|
|
6449
5620
|
_Source: src/core/nodes/group-node.ts_
|
|
@@ -6452,38 +5623,44 @@ _Source: src/core/nodes/group-node.ts_
|
|
|
6452
5623
|
|
|
6453
5624
|
**Kind:** `interface`
|
|
6454
5625
|
|
|
6455
|
-
Конфигурация GroupNode
|
|
6456
|
-
|
|
5626
|
+
Конфигурация GroupNode.
|
|
5627
|
+
|
|
5628
|
+
Под M1 группа создаётся из плоской {@link FormSchema} (дерево field-конфигов). Обёртка
|
|
5629
|
+
`{ form }` сохранена для совместимости вызова, legacy behavior/validation-схемы удалены (Ф7).
|
|
6457
5630
|
|
|
6458
5631
|
**Signature:**
|
|
6459
5632
|
```typescript
|
|
6460
5633
|
export interface GroupNodeConfig<T> {
|
|
6461
5634
|
/** Схема структуры формы (поля и их конфигурация) */
|
|
6462
5635
|
form: FormSchema<T>;
|
|
5636
|
+
}
|
|
5637
|
+
```
|
|
6463
5638
|
|
|
6464
|
-
|
|
6465
|
-
behavior?: BehaviorSchemaFn<T>;
|
|
5639
|
+
_Source: src/core/types/index.ts_
|
|
6466
5640
|
|
|
6467
|
-
|
|
6468
|
-
validation?: ValidationSchemaFn<T>;
|
|
5641
|
+
### integer
|
|
6469
5642
|
|
|
6470
|
-
|
|
6471
|
-
* Опциональный ValidationRegistry для dependency injection
|
|
6472
|
-
* Используется для тестирования с mock-реестрами
|
|
6473
|
-
* @internal
|
|
6474
|
-
*/
|
|
6475
|
-
_validationRegistry?: unknown;
|
|
5643
|
+
**Kind:** `function`
|
|
6476
5644
|
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
5645
|
+
Фабрика валидатора, проверяющего что число — целое.
|
|
5646
|
+
|
|
5647
|
+
Пустые значения и не-числа пропускаются (используйте `required` и `isNumber`).
|
|
5648
|
+
|
|
5649
|
+
**Signature:**
|
|
5650
|
+
```typescript
|
|
5651
|
+
export function integer<TForm = unknown, TField extends number | undefined = number>(
|
|
5652
|
+
options?: ValidateOptions
|
|
5653
|
+
): Validator<TForm, TField>
|
|
6484
5654
|
```
|
|
6485
5655
|
|
|
6486
|
-
|
|
5656
|
+
**Examples:**
|
|
5657
|
+
|
|
5658
|
+
```typescript
|
|
5659
|
+
validate(path.age, integer());
|
|
5660
|
+
validate(path.count, integer({ message: 'Должно быть целым числом' }));
|
|
5661
|
+
```
|
|
5662
|
+
|
|
5663
|
+
_Source: src/core/validation/validators/integer.ts_
|
|
6487
5664
|
|
|
6488
5665
|
### isArrayNode
|
|
6489
5666
|
|
|
@@ -6520,45 +5697,38 @@ _Source: src/core/utils/type-guards.ts_
|
|
|
6520
5697
|
|
|
6521
5698
|
**Kind:** `function`
|
|
6522
5699
|
|
|
6523
|
-
|
|
5700
|
+
Фабрика валидатора, проверяющего что значение — валидная дата.
|
|
6524
5701
|
|
|
6525
5702
|
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
6526
5703
|
|
|
6527
5704
|
**Signature:**
|
|
6528
5705
|
```typescript
|
|
6529
|
-
export function isDate<TForm, TField extends string | Date | undefined = string | Date>(
|
|
6530
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
5706
|
+
export function isDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(
|
|
6531
5707
|
options?: ValidateOptions
|
|
6532
|
-
):
|
|
5708
|
+
): Validator<TForm, TField>
|
|
6533
5709
|
```
|
|
6534
5710
|
|
|
6535
|
-
**Parameters:**
|
|
6536
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6537
|
-
- `options` — - Опции валидации (message, params)
|
|
6538
|
-
|
|
6539
5711
|
**Examples:**
|
|
6540
5712
|
|
|
6541
5713
|
```typescript
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
isDate(path.birthDate),
|
|
6545
|
-
isDate(path.eventDate),
|
|
6546
|
-
]
|
|
6547
|
-
|
|
6548
|
-
// С кастомным сообщением
|
|
6549
|
-
isDate(path.birthDate, { message: 'Введите корректную дату' })
|
|
5714
|
+
validate(path.birthDate, isDate());
|
|
5715
|
+
validate(path.eventDate, isDate({ message: 'Введите корректную дату' }));
|
|
6550
5716
|
```
|
|
6551
5717
|
|
|
5718
|
+
_Source: src/core/validation/validators/is-date.ts_
|
|
5719
|
+
|
|
5720
|
+
### isDerived
|
|
5721
|
+
|
|
5722
|
+
**Kind:** `function`
|
|
5723
|
+
|
|
5724
|
+
Производный ли сигнал (читают bulk-сеттеры, чтобы не затирать вычисляемые поля).
|
|
5725
|
+
|
|
5726
|
+
**Signature:**
|
|
6552
5727
|
```typescript
|
|
6553
|
-
|
|
6554
|
-
{
|
|
6555
|
-
code: 'date_invalid',
|
|
6556
|
-
message: 'Неверный формат даты',
|
|
6557
|
-
params: {}
|
|
6558
|
-
}
|
|
5728
|
+
export function isDerived(signal: Signal<any>): boolean
|
|
6559
5729
|
```
|
|
6560
5730
|
|
|
6561
|
-
_Source: src/core/
|
|
5731
|
+
_Source: src/core/utils/derived-registry.ts_
|
|
6562
5732
|
|
|
6563
5733
|
### isFieldNode
|
|
6564
5734
|
|
|
@@ -6626,8 +5796,8 @@ _Source: src/core/utils/type-guards.ts_
|
|
|
6626
5796
|
|
|
6627
5797
|
Проверить, является ли значение GroupNode (объект с вложенными полями)
|
|
6628
5798
|
|
|
6629
|
-
GroupNode представляет объект с вложенными полями
|
|
6630
|
-
и имеет
|
|
5799
|
+
GroupNode представляет объект с вложенными полями формы: имеет навигацию по полям
|
|
5800
|
+
(`getFieldByPath`/`fields`) и НЕ имеет array-методов (`items`/`push`/`removeAt`).
|
|
6631
5801
|
|
|
6632
5802
|
**Signature:**
|
|
6633
5803
|
```typescript
|
|
@@ -6643,101 +5813,99 @@ export function isGroupNode(value: unknown): value is GroupNode<FormFields>
|
|
|
6643
5813
|
|
|
6644
5814
|
```typescript
|
|
6645
5815
|
if (isGroupNode(node)) {
|
|
6646
|
-
node.applyValidationSchema(schema); // OK
|
|
6647
5816
|
node.getFieldByPath('user.email'); // OK
|
|
6648
5817
|
}
|
|
6649
5818
|
```
|
|
6650
5819
|
|
|
6651
5820
|
_Source: src/core/utils/type-guards.ts_
|
|
6652
5821
|
|
|
6653
|
-
###
|
|
5822
|
+
### isNumber
|
|
6654
5823
|
|
|
6655
5824
|
**Kind:** `function`
|
|
6656
5825
|
|
|
6657
|
-
|
|
5826
|
+
Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).
|
|
6658
5827
|
|
|
6659
|
-
|
|
6660
|
-
|
|
5828
|
+
Пустые значения (`null`/`undefined`) пропускаются (используйте `required` для обязательности).
|
|
5829
|
+
В отличие от других number-валидаторов, **не** пропускает не-числа — это его задача.
|
|
6661
5830
|
|
|
6662
5831
|
**Signature:**
|
|
6663
5832
|
```typescript
|
|
6664
|
-
export function
|
|
6665
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
6666
|
-
maxValue: number,
|
|
5833
|
+
export function isNumber<TForm = unknown, TField extends number | undefined = number>(
|
|
6667
5834
|
options?: ValidateOptions
|
|
6668
|
-
):
|
|
5835
|
+
): Validator<TForm, TField>
|
|
6669
5836
|
```
|
|
6670
5837
|
|
|
6671
|
-
**Parameters:**
|
|
6672
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6673
|
-
- `maxValue` — - Максимально допустимое значение
|
|
6674
|
-
- `options` — - Опции валидации (message, params)
|
|
6675
|
-
|
|
6676
5838
|
**Examples:**
|
|
6677
5839
|
|
|
6678
5840
|
```typescript
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
max(path.quantity, 100),
|
|
6682
|
-
max(path.discount, 50),
|
|
6683
|
-
]
|
|
6684
|
-
|
|
6685
|
-
// С кастомным сообщением
|
|
6686
|
-
max(path.quantity, 100, { message: 'Максимум 100 единиц' })
|
|
5841
|
+
validate(path.amount, isNumber());
|
|
5842
|
+
validate(path.amount, isNumber({ message: 'Введите число' }));
|
|
6687
5843
|
```
|
|
6688
5844
|
|
|
5845
|
+
_Source: src/core/validation/validators/is-number.ts_
|
|
5846
|
+
|
|
5847
|
+
### markDerived
|
|
5848
|
+
|
|
5849
|
+
**Kind:** `function`
|
|
5850
|
+
|
|
5851
|
+
Пометить сигнал производным (вызывают compute/computeFrom).
|
|
5852
|
+
|
|
5853
|
+
**Signature:**
|
|
6689
5854
|
```typescript
|
|
6690
|
-
|
|
6691
|
-
{
|
|
6692
|
-
code: 'max',
|
|
6693
|
-
message: 'Максимальное значение: 100',
|
|
6694
|
-
params: { max: 100, actual: 150 }
|
|
6695
|
-
}
|
|
5855
|
+
export function markDerived(signal: Signal<any>): void
|
|
6696
5856
|
```
|
|
6697
5857
|
|
|
6698
|
-
_Source: src/core/
|
|
5858
|
+
_Source: src/core/utils/derived-registry.ts_
|
|
6699
5859
|
|
|
6700
|
-
###
|
|
5860
|
+
### max
|
|
6701
5861
|
|
|
6702
5862
|
**Kind:** `function`
|
|
6703
5863
|
|
|
6704
|
-
|
|
5864
|
+
Фабрика валидатора максимального числового значения.
|
|
6705
5865
|
|
|
6706
|
-
Пустые значения
|
|
5866
|
+
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
6707
5867
|
|
|
6708
5868
|
**Signature:**
|
|
6709
5869
|
```typescript
|
|
6710
|
-
export function
|
|
6711
|
-
|
|
6712
|
-
maxAgeValue: number,
|
|
5870
|
+
export function max<TForm = unknown, TField extends number | undefined = number>(
|
|
5871
|
+
maxValue: number,
|
|
6713
5872
|
options?: ValidateOptions
|
|
6714
|
-
):
|
|
5873
|
+
): Validator<TForm, TField>
|
|
6715
5874
|
```
|
|
6716
5875
|
|
|
6717
|
-
**Parameters:**
|
|
6718
|
-
- `fieldPath` — - Путь к полю для валидации (дата рождения)
|
|
6719
|
-
- `maxAgeValue` — - Максимально допустимый возраст в годах
|
|
6720
|
-
- `options` — - Опции валидации (message, params)
|
|
6721
|
-
|
|
6722
5876
|
**Examples:**
|
|
6723
5877
|
|
|
6724
5878
|
```typescript
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
maxAge(path.birthDate, 65),
|
|
6728
|
-
]
|
|
5879
|
+
import { validate } from '@reformer/core';
|
|
5880
|
+
import { max } from '@reformer/core/validators';
|
|
6729
5881
|
|
|
6730
|
-
|
|
6731
|
-
|
|
5882
|
+
validate(path.quantity, max(100));
|
|
5883
|
+
validate(path.discount, max(50, { message: 'Не более 50%' }));
|
|
6732
5884
|
```
|
|
6733
5885
|
|
|
5886
|
+
_Source: src/core/validation/validators/max.ts_
|
|
5887
|
+
|
|
5888
|
+
### maxAge
|
|
5889
|
+
|
|
5890
|
+
**Kind:** `function`
|
|
5891
|
+
|
|
5892
|
+
Фабрика валидатора максимального возраста (по дате рождения).
|
|
5893
|
+
|
|
5894
|
+
Пустые и невалидные даты пропускаются.
|
|
5895
|
+
|
|
5896
|
+
**Signature:**
|
|
6734
5897
|
```typescript
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
5898
|
+
export function maxAge<
|
|
5899
|
+
TForm = unknown,
|
|
5900
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
5901
|
+
>(maxAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
|
|
5902
|
+
```
|
|
5903
|
+
|
|
5904
|
+
**Examples:**
|
|
5905
|
+
|
|
5906
|
+
```typescript
|
|
5907
|
+
validate(path.birthDate, maxAge(65));
|
|
5908
|
+
validate(path.birthDate, maxAge(100, { message: 'Проверьте дату рождения' }));
|
|
6741
5909
|
```
|
|
6742
5910
|
|
|
6743
5911
|
_Source: src/core/validation/validators/max-age.ts_
|
|
@@ -6746,43 +5914,23 @@ _Source: src/core/validation/validators/max-age.ts_
|
|
|
6746
5914
|
|
|
6747
5915
|
**Kind:** `function`
|
|
6748
5916
|
|
|
6749
|
-
|
|
5917
|
+
Фабрика валидатора максимальной даты (включительно).
|
|
6750
5918
|
|
|
6751
|
-
Пустые
|
|
5919
|
+
Пустые и невалидные даты пропускаются.
|
|
6752
5920
|
|
|
6753
5921
|
**Signature:**
|
|
6754
5922
|
```typescript
|
|
6755
|
-
export function maxDate<
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
): void
|
|
5923
|
+
export function maxDate<
|
|
5924
|
+
TForm = unknown,
|
|
5925
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
5926
|
+
>(maxDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
|
|
6760
5927
|
```
|
|
6761
5928
|
|
|
6762
|
-
**Parameters:**
|
|
6763
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6764
|
-
- `maxDateValue` — - Максимально допустимая дата (включительно)
|
|
6765
|
-
- `options` — - Опции валидации (message, params)
|
|
6766
|
-
|
|
6767
5929
|
**Examples:**
|
|
6768
5930
|
|
|
6769
5931
|
```typescript
|
|
6770
|
-
//
|
|
6771
|
-
|
|
6772
|
-
maxDate(path.birthDate, new Date()), // Не позже сегодня
|
|
6773
|
-
]
|
|
6774
|
-
|
|
6775
|
-
// С кастомным сообщением
|
|
6776
|
-
maxDate(path.endDate, new Date('2025-12-31'), { message: 'Дата не может быть позже конца года' })
|
|
6777
|
-
```
|
|
6778
|
-
|
|
6779
|
-
```typescript
|
|
6780
|
-
// Ошибка валидации
|
|
6781
|
-
{
|
|
6782
|
-
code: 'date_max',
|
|
6783
|
-
message: 'Дата должна быть не позднее 31.12.2025',
|
|
6784
|
-
params: { maxDate: Date }
|
|
6785
|
-
}
|
|
5932
|
+
validate(path.birthDate, maxDate(new Date())); // не позже сегодня
|
|
5933
|
+
validate(path.endDate, maxDate(new Date('2025-12-31')));
|
|
6786
5934
|
```
|
|
6787
5935
|
|
|
6788
5936
|
_Source: src/core/validation/validators/max-date.ts_
|
|
@@ -6791,45 +5939,23 @@ _Source: src/core/validation/validators/max-date.ts_
|
|
|
6791
5939
|
|
|
6792
5940
|
**Kind:** `function`
|
|
6793
5941
|
|
|
6794
|
-
|
|
5942
|
+
Фабрика валидатора максимальной длины строки или массива.
|
|
6795
5943
|
|
|
6796
|
-
Проверяет, что длина строки не превышает указанный максимум.
|
|
6797
5944
|
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
6798
5945
|
|
|
6799
5946
|
**Signature:**
|
|
6800
5947
|
```typescript
|
|
6801
|
-
export function maxLength<TForm, TField
|
|
6802
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
5948
|
+
export function maxLength<TForm = unknown, TField = unknown>(
|
|
6803
5949
|
maxLen: number,
|
|
6804
5950
|
options?: ValidateOptions
|
|
6805
|
-
):
|
|
5951
|
+
): Validator<TForm, TField>
|
|
6806
5952
|
```
|
|
6807
5953
|
|
|
6808
|
-
**Parameters:**
|
|
6809
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6810
|
-
- `maxLen` — - Максимальная допустимая длина строки
|
|
6811
|
-
- `options` — - Опции валидации (message, params)
|
|
6812
|
-
|
|
6813
5954
|
**Examples:**
|
|
6814
5955
|
|
|
6815
5956
|
```typescript
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
maxLength(path.name, 50),
|
|
6819
|
-
maxLength(path.bio, 500),
|
|
6820
|
-
]
|
|
6821
|
-
|
|
6822
|
-
// С кастомным сообщением
|
|
6823
|
-
maxLength(path.bio, 500, { message: 'Максимум 500 символов' })
|
|
6824
|
-
```
|
|
6825
|
-
|
|
6826
|
-
```typescript
|
|
6827
|
-
// Ошибка валидации
|
|
6828
|
-
{
|
|
6829
|
-
code: 'maxLength',
|
|
6830
|
-
message: 'Максимальная длина: 500 символов',
|
|
6831
|
-
params: { maxLength: 500, actualLength: 512 }
|
|
6832
|
-
}
|
|
5957
|
+
validate(path.name, maxLength(50));
|
|
5958
|
+
validate(path.bio, maxLength(500, { message: 'Max 500 символов' }));
|
|
6833
5959
|
```
|
|
6834
5960
|
|
|
6835
5961
|
_Source: src/core/validation/validators/max-length.ts_
|
|
@@ -6838,45 +5964,26 @@ _Source: src/core/validation/validators/max-length.ts_
|
|
|
6838
5964
|
|
|
6839
5965
|
**Kind:** `function`
|
|
6840
5966
|
|
|
6841
|
-
|
|
5967
|
+
Фабрика валидатора минимального числового значения.
|
|
6842
5968
|
|
|
6843
|
-
Проверяет, что числовое значение не меньше указанного минимума.
|
|
6844
5969
|
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
6845
5970
|
|
|
6846
5971
|
**Signature:**
|
|
6847
5972
|
```typescript
|
|
6848
|
-
export function min<TForm, TField extends number |
|
|
6849
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
5973
|
+
export function min<TForm = unknown, TField extends number | undefined = number>(
|
|
6850
5974
|
minValue: number,
|
|
6851
5975
|
options?: ValidateOptions
|
|
6852
|
-
):
|
|
5976
|
+
): Validator<TForm, TField>
|
|
6853
5977
|
```
|
|
6854
5978
|
|
|
6855
|
-
**Parameters:**
|
|
6856
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6857
|
-
- `minValue` — - Минимально допустимое значение
|
|
6858
|
-
- `options` — - Опции валидации (message, params)
|
|
6859
|
-
|
|
6860
5979
|
**Examples:**
|
|
6861
5980
|
|
|
6862
5981
|
```typescript
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
min(path.age, 18),
|
|
6866
|
-
min(path.quantity, 1),
|
|
6867
|
-
]
|
|
6868
|
-
|
|
6869
|
-
// С кастомным сообщением
|
|
6870
|
-
min(path.age, 18, { message: 'Вам должно быть не менее 18 лет' })
|
|
6871
|
-
```
|
|
5982
|
+
import { validate } from '@reformer/core';
|
|
5983
|
+
import { min } from '@reformer/core/validators';
|
|
6872
5984
|
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
{
|
|
6876
|
-
code: 'min',
|
|
6877
|
-
message: 'Минимальное значение: 18',
|
|
6878
|
-
params: { min: 18, actual: 16 }
|
|
6879
|
-
}
|
|
5985
|
+
validate(path.age, min(18));
|
|
5986
|
+
validate(path.quantity, min(1, { message: 'Минимум 1' }));
|
|
6880
5987
|
```
|
|
6881
5988
|
|
|
6882
5989
|
_Source: src/core/validation/validators/min.ts_
|
|
@@ -6885,138 +5992,304 @@ _Source: src/core/validation/validators/min.ts_
|
|
|
6885
5992
|
|
|
6886
5993
|
**Kind:** `function`
|
|
6887
5994
|
|
|
6888
|
-
|
|
5995
|
+
Фабрика валидатора минимального возраста (по дате рождения).
|
|
6889
5996
|
|
|
6890
|
-
Пустые
|
|
5997
|
+
Пустые и невалидные даты пропускаются.
|
|
6891
5998
|
|
|
6892
5999
|
**Signature:**
|
|
6893
6000
|
```typescript
|
|
6894
|
-
export function minAge<
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
): void
|
|
6001
|
+
export function minAge<
|
|
6002
|
+
TForm = unknown,
|
|
6003
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
6004
|
+
>(minAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
|
|
6899
6005
|
```
|
|
6900
6006
|
|
|
6901
|
-
**Parameters:**
|
|
6902
|
-
- `fieldPath` — - Путь к полю для валидации (дата рождения)
|
|
6903
|
-
- `minAgeValue` — - Минимально допустимый возраст в годах
|
|
6904
|
-
- `options` — - Опции валидации (message, params)
|
|
6905
|
-
|
|
6906
6007
|
**Examples:**
|
|
6907
6008
|
|
|
6908
6009
|
```typescript
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6010
|
+
validate(path.birthDate, minAge(18));
|
|
6011
|
+
validate(path.birthDate, minAge(21, { message: 'Вам должно быть не менее 21 года' }));
|
|
6012
|
+
```
|
|
6013
|
+
|
|
6014
|
+
_Source: src/core/validation/validators/min-age.ts_
|
|
6015
|
+
|
|
6016
|
+
### minDate
|
|
6017
|
+
|
|
6018
|
+
**Kind:** `function`
|
|
6019
|
+
|
|
6020
|
+
Фабрика валидатора минимальной даты (включительно).
|
|
6913
6021
|
|
|
6914
|
-
|
|
6915
|
-
|
|
6022
|
+
Пустые и невалидные даты пропускаются (используйте `required` и `isDate`).
|
|
6023
|
+
|
|
6024
|
+
**Signature:**
|
|
6025
|
+
```typescript
|
|
6026
|
+
export function minDate<
|
|
6027
|
+
TForm = unknown,
|
|
6028
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
6029
|
+
>(minDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
|
|
6916
6030
|
```
|
|
6917
6031
|
|
|
6032
|
+
**Examples:**
|
|
6033
|
+
|
|
6918
6034
|
```typescript
|
|
6919
|
-
|
|
6920
|
-
{
|
|
6921
|
-
code: 'date_min_age',
|
|
6922
|
-
message: 'Минимальный возраст: 18 лет',
|
|
6923
|
-
params: { minAge: 18, currentAge: 16 }
|
|
6924
|
-
}
|
|
6035
|
+
validate(path.eventDate, minDate(new Date('2024-01-01')));
|
|
6036
|
+
validate(path.startDate, minDate(new Date(), { message: 'Дата должна быть не раньше сегодня' }));
|
|
6925
6037
|
```
|
|
6926
6038
|
|
|
6927
|
-
_Source: src/core/validation/validators/min-
|
|
6039
|
+
_Source: src/core/validation/validators/min-date.ts_
|
|
6928
6040
|
|
|
6929
|
-
###
|
|
6041
|
+
### minLength
|
|
6930
6042
|
|
|
6931
6043
|
**Kind:** `function`
|
|
6932
6044
|
|
|
6933
|
-
|
|
6045
|
+
Фабрика валидатора минимальной длины строки или массива.
|
|
6934
6046
|
|
|
6935
|
-
Пустые значения
|
|
6047
|
+
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
6936
6048
|
|
|
6937
6049
|
**Signature:**
|
|
6938
6050
|
```typescript
|
|
6939
|
-
export function
|
|
6940
|
-
|
|
6941
|
-
minDateValue: Date,
|
|
6051
|
+
export function minLength<TForm = unknown, TField = unknown>(
|
|
6052
|
+
minLen: number,
|
|
6942
6053
|
options?: ValidateOptions
|
|
6943
|
-
):
|
|
6054
|
+
): Validator<TForm, TField>
|
|
6944
6055
|
```
|
|
6945
6056
|
|
|
6946
|
-
**Parameters:**
|
|
6947
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6948
|
-
- `minDateValue` — - Минимально допустимая дата (включительно)
|
|
6949
|
-
- `options` — - Опции валидации (message, params)
|
|
6950
|
-
|
|
6951
6057
|
**Examples:**
|
|
6952
6058
|
|
|
6953
6059
|
```typescript
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6060
|
+
validate(path.name, minLength(2));
|
|
6061
|
+
validate(path.password, minLength(8, { message: 'Min 8 символов' }));
|
|
6062
|
+
```
|
|
6063
|
+
|
|
6064
|
+
_Source: src/core/validation/validators/min-length.ts_
|
|
6065
|
+
|
|
6066
|
+
### ModelApi
|
|
6067
|
+
|
|
6068
|
+
**Kind:** `interface`
|
|
6958
6069
|
|
|
6959
|
-
|
|
6960
|
-
|
|
6070
|
+
API уровня модели (доступно на корне и под-моделях элементов массива).
|
|
6071
|
+
|
|
6072
|
+
⚠️ Имена методов (`$`/`get`/`set`/`patch`/`isDirty`/`reset`/`signalAt`/`captureInitial`)
|
|
6073
|
+
зарезервированы: одноимённое поле формы их затеняет (редкий краевой случай).
|
|
6074
|
+
|
|
6075
|
+
**Signature:**
|
|
6076
|
+
```typescript
|
|
6077
|
+
export interface ModelApi<T> {
|
|
6078
|
+
/** Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`. */
|
|
6079
|
+
readonly $: ModelSignals<T>;
|
|
6080
|
+
/** Снимок значений (без подписки) — для submit. */
|
|
6081
|
+
get(): T;
|
|
6082
|
+
/** Массовая замена значений (load с сервера). Не меняет initial-снимок. */
|
|
6083
|
+
set(value: Partial<T>): void;
|
|
6084
|
+
/** Частичное слияние значений. */
|
|
6085
|
+
patch(value: Partial<T>): void;
|
|
6086
|
+
/** Отличаются ли текущие значения от initial-снимка (value-diff). */
|
|
6087
|
+
isDirty(): boolean;
|
|
6088
|
+
/** Сбросить значения к initial-снимку. */
|
|
6089
|
+
reset(): void;
|
|
6090
|
+
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
6091
|
+
captureInitial(): void;
|
|
6092
|
+
/** Резолв строкового пути в сигнал (для error-routing/мостов). */
|
|
6093
|
+
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
6094
|
+
}
|
|
6961
6095
|
```
|
|
6962
6096
|
|
|
6097
|
+
_Source: src/core/model/types.ts_
|
|
6098
|
+
|
|
6099
|
+
### ModelArray
|
|
6100
|
+
|
|
6101
|
+
**Kind:** `interface`
|
|
6102
|
+
|
|
6103
|
+
Реактивный массив модели. Мутации (`push`/`removeAt`/…) меняют длину реактивно;
|
|
6104
|
+
`map`/`forEach`/`at` отдают под-модель элемента ({@link FormModel}) для объектных элементов.
|
|
6105
|
+
|
|
6106
|
+
**Signature:**
|
|
6963
6107
|
```typescript
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6108
|
+
export interface ModelArray<U> {
|
|
6109
|
+
/** Реактивная длина. */
|
|
6110
|
+
readonly length: number;
|
|
6111
|
+
/** Добавить элемент в конец (значение элемента целиком). */
|
|
6112
|
+
push(item: U): void;
|
|
6113
|
+
/** Вставить элемент по индексу. */
|
|
6114
|
+
insertAt(index: number, item: U): void;
|
|
6115
|
+
/** Удалить элемент по индексу. */
|
|
6116
|
+
removeAt(index: number): void;
|
|
6117
|
+
/** Переместить элемент. */
|
|
6118
|
+
move(from: number, to: number): void;
|
|
6119
|
+
/** Поменять местами два элемента. */
|
|
6120
|
+
swap(a: number, b: number): void;
|
|
6121
|
+
/** Очистить массив. */
|
|
6122
|
+
clear(): void;
|
|
6123
|
+
/** Под-модель элемента по индексу (для объектных элементов) или значение (для примитивных). */
|
|
6124
|
+
at(index: number): U extends object ? FormModel<U> : U | undefined;
|
|
6125
|
+
/** Map по элементам: объектные → {@link FormModel}, примитивные → значение. */
|
|
6126
|
+
map<R>(fn: (item: U extends object ? FormModel<U> : U, index: number) => R): R[];
|
|
6127
|
+
/** Итерация по элементам. */
|
|
6128
|
+
forEach(fn: (item: U extends object ? FormModel<U> : U, index: number) => void): void;
|
|
6129
|
+
/** Снимок массива значений (без подписки). */
|
|
6130
|
+
toArray(): U[];
|
|
6131
|
+
/** Индексный value-доступ. */
|
|
6132
|
+
[index: number]: ModelValue<U>;
|
|
6969
6133
|
}
|
|
6970
6134
|
```
|
|
6971
6135
|
|
|
6972
|
-
_Source: src/core/
|
|
6136
|
+
_Source: src/core/model/types.ts_
|
|
6973
6137
|
|
|
6974
|
-
###
|
|
6138
|
+
### ModelArrayControl
|
|
6975
6139
|
|
|
6976
|
-
**Kind:** `
|
|
6140
|
+
**Kind:** `interface`
|
|
6977
6141
|
|
|
6978
|
-
|
|
6142
|
+
Минимальный контракт реактивного массива модели, используемый узлом.
|
|
6979
6143
|
|
|
6980
|
-
|
|
6981
|
-
|
|
6144
|
+
**Signature:**
|
|
6145
|
+
```typescript
|
|
6146
|
+
export interface ModelArrayControl<TItem extends object> {
|
|
6147
|
+
readonly length: number;
|
|
6148
|
+
at(index: number): FormModel<TItem> | undefined;
|
|
6149
|
+
push(item: TItem): void;
|
|
6150
|
+
insertAt(index: number, item: TItem): void;
|
|
6151
|
+
removeAt(index: number): void;
|
|
6152
|
+
move(from: number, to: number): void;
|
|
6153
|
+
swap(a: number, b: number): void;
|
|
6154
|
+
clear(): void;
|
|
6155
|
+
toArray(): TItem[];
|
|
6156
|
+
}
|
|
6157
|
+
```
|
|
6158
|
+
|
|
6159
|
+
_Source: src/core/nodes/model-array-node.ts_
|
|
6160
|
+
|
|
6161
|
+
### ModelArrayNode
|
|
6162
|
+
|
|
6163
|
+
**Kind:** `class`
|
|
6982
6164
|
|
|
6983
6165
|
**Signature:**
|
|
6984
6166
|
```typescript
|
|
6985
|
-
export
|
|
6986
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
6987
|
-
minLen: number,
|
|
6988
|
-
options?: ValidateOptions
|
|
6989
|
-
): void
|
|
6167
|
+
export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */ }
|
|
6990
6168
|
```
|
|
6991
6169
|
|
|
6992
|
-
|
|
6993
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
6994
|
-
- `minLen` — - Минимальная допустимая длина строки
|
|
6995
|
-
- `options` — - Опции валидации (message, params)
|
|
6170
|
+
_Source: src/core/nodes/model-array-node.ts_
|
|
6996
6171
|
|
|
6997
|
-
|
|
6172
|
+
### ModelObject
|
|
6173
|
+
|
|
6174
|
+
**Kind:** `type`
|
|
6175
|
+
|
|
6176
|
+
value-proxy объекта: поля доступны как обычные свойства (чтение реактивно
|
|
6177
|
+
внутри `effect`/`computed`, запись — присваиванием).
|
|
6998
6178
|
|
|
6179
|
+
**Signature:**
|
|
6999
6180
|
```typescript
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
6181
|
+
export type ModelObject<T> = {
|
|
6182
|
+
[K in keyof T]: ModelValue<T[K]>;
|
|
6183
|
+
};
|
|
6184
|
+
```
|
|
6185
|
+
|
|
6186
|
+
_Source: src/core/model/types.ts_
|
|
6187
|
+
|
|
6188
|
+
### ModelSignals
|
|
6189
|
+
|
|
6190
|
+
**Kind:** `type`
|
|
6191
|
+
|
|
6192
|
+
Дерево сигналов (escape-hatch `model.$`): листья → {@link PathAwareSignal},
|
|
6193
|
+
объекты → вложенное дерево, массивы → индексируемое дерево под-сигналов.
|
|
7005
6194
|
|
|
7006
|
-
|
|
7007
|
-
|
|
6195
|
+
**Signature:**
|
|
6196
|
+
```typescript
|
|
6197
|
+
export type ModelSignals<T> = {
|
|
6198
|
+
[K in keyof T]: NonNullable<T[K]> extends ReadonlyArray<infer U>
|
|
6199
|
+
? { readonly length: number; readonly [index: number]: ModelSignals<U & object> }
|
|
6200
|
+
: NonNullable<T[K]> extends Opaque
|
|
6201
|
+
? PathAwareSignal<T[K]>
|
|
6202
|
+
: NonNullable<T[K]> extends object
|
|
6203
|
+
? ModelSignals<NonNullable<T[K]>>
|
|
6204
|
+
: PathAwareSignal<T[K]>;
|
|
6205
|
+
};
|
|
7008
6206
|
```
|
|
7009
6207
|
|
|
6208
|
+
_Source: src/core/model/types.ts_
|
|
6209
|
+
|
|
6210
|
+
### ModelValidationResult
|
|
6211
|
+
|
|
6212
|
+
**Kind:** `interface`
|
|
6213
|
+
|
|
6214
|
+
Результат валидации: ошибки по пути поля.
|
|
6215
|
+
|
|
6216
|
+
**Signature:**
|
|
7010
6217
|
```typescript
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
params: { minLength: 8, actualLength: 3 }
|
|
6218
|
+
export interface ModelValidationResult {
|
|
6219
|
+
valid: boolean;
|
|
6220
|
+
/** Ключ — путь поля (`'coBorrowers.0.relationship'`). Только поля с ошибками. */
|
|
6221
|
+
errors: Record<string, ValidationError[]>;
|
|
7016
6222
|
}
|
|
7017
6223
|
```
|
|
7018
6224
|
|
|
7019
|
-
_Source: src/core/
|
|
6225
|
+
_Source: src/core/model/validate-model.ts_
|
|
6226
|
+
|
|
6227
|
+
### ModelValidator
|
|
6228
|
+
|
|
6229
|
+
**Kind:** `type`
|
|
6230
|
+
|
|
6231
|
+
Валидатор слоя данных. `value` — значение поля; `model` — ближайший scope (под-модель элемента
|
|
6232
|
+
массива или корень); `root` — корневая модель.
|
|
6233
|
+
|
|
6234
|
+
**Signature:**
|
|
6235
|
+
```typescript
|
|
6236
|
+
export type ModelValidator<TValue = unknown, TModel = unknown, TRoot = unknown> = (
|
|
6237
|
+
value: TValue,
|
|
6238
|
+
model: TModel,
|
|
6239
|
+
root: TRoot
|
|
6240
|
+
) => ValidationError | null | Promise<ValidationError | null>;
|
|
6241
|
+
```
|
|
6242
|
+
|
|
6243
|
+
_Source: src/core/model/validate-model.ts_
|
|
6244
|
+
|
|
6245
|
+
### ModelValue
|
|
6246
|
+
|
|
6247
|
+
**Kind:** `type`
|
|
6248
|
+
|
|
6249
|
+
Значение поля в value-доступе модели:
|
|
6250
|
+
- массив → {@link ModelArray}
|
|
6251
|
+
- спец-объект (Date/File/Blob) → как есть
|
|
6252
|
+
- объект → вложенный value-proxy
|
|
6253
|
+
- примитив → значение
|
|
6254
|
+
|
|
6255
|
+
**Signature:**
|
|
6256
|
+
```typescript
|
|
6257
|
+
export type ModelValue<V> =
|
|
6258
|
+
NonNullable<V> extends ReadonlyArray<infer U>
|
|
6259
|
+
? ModelArray<U>
|
|
6260
|
+
: NonNullable<V> extends Opaque
|
|
6261
|
+
? V
|
|
6262
|
+
: NonNullable<V> extends object
|
|
6263
|
+
? ModelObject<NonNullable<V>>
|
|
6264
|
+
: V;
|
|
6265
|
+
```
|
|
6266
|
+
|
|
6267
|
+
_Source: src/core/model/types.ts_
|
|
6268
|
+
|
|
6269
|
+
### multipleOf
|
|
6270
|
+
|
|
6271
|
+
**Kind:** `function`
|
|
6272
|
+
|
|
6273
|
+
Фабрика валидатора, проверяющего что число кратно заданному.
|
|
6274
|
+
|
|
6275
|
+
Пустые значения и не-числа пропускаются (используйте `required` и `isNumber`).
|
|
6276
|
+
|
|
6277
|
+
**Signature:**
|
|
6278
|
+
```typescript
|
|
6279
|
+
export function multipleOf<TForm = unknown, TField extends number | undefined = number>(
|
|
6280
|
+
divisor: number,
|
|
6281
|
+
options?: ValidateOptions
|
|
6282
|
+
): Validator<TForm, TField>
|
|
6283
|
+
```
|
|
6284
|
+
|
|
6285
|
+
**Examples:**
|
|
6286
|
+
|
|
6287
|
+
```typescript
|
|
6288
|
+
validate(path.price, multipleOf(0.01));
|
|
6289
|
+
validate(path.rating, multipleOf(0.5, { message: 'Только шаг 0.5' }));
|
|
6290
|
+
```
|
|
6291
|
+
|
|
6292
|
+
_Source: src/core/validation/validators/multiple-of.ts_
|
|
7020
6293
|
|
|
7021
6294
|
### NodeFactory
|
|
7022
6295
|
|
|
@@ -7087,79 +6360,53 @@ const node = NodeFactory.create({ value: '' }); // → FieldNode<string>
|
|
|
7087
6360
|
|
|
7088
6361
|
_Source: src/core/factories/node-factory.ts_
|
|
7089
6362
|
|
|
7090
|
-
###
|
|
6363
|
+
### nonNegative
|
|
7091
6364
|
|
|
7092
6365
|
**Kind:** `function`
|
|
7093
6366
|
|
|
7094
|
-
|
|
6367
|
+
Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).
|
|
7095
6368
|
|
|
7096
|
-
|
|
6369
|
+
Пустые значения и не-числа пропускаются (используйте `required` и `isNumber`).
|
|
7097
6370
|
|
|
7098
6371
|
**Signature:**
|
|
7099
6372
|
```typescript
|
|
7100
|
-
export function
|
|
7101
|
-
fieldPath: FieldPathNode<TForm, TItem[] | undefined> | undefined,
|
|
6373
|
+
export function nonNegative<TForm = unknown, TField extends number | undefined = number>(
|
|
7102
6374
|
options?: ValidateOptions
|
|
7103
|
-
):
|
|
6375
|
+
): Validator<TForm, TField>
|
|
7104
6376
|
```
|
|
7105
6377
|
|
|
7106
|
-
**Parameters:**
|
|
7107
|
-
- `fieldPath` — - Поле-массив для валидации
|
|
7108
|
-
- `options` — - Опции валидации (message, params и т.д.)
|
|
7109
|
-
|
|
7110
6378
|
**Examples:**
|
|
7111
6379
|
|
|
7112
6380
|
```typescript
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
// С дополнительными параметрами
|
|
7117
|
-
notEmpty(path.coBorrowers, {
|
|
7118
|
-
message: 'Требуется хотя бы один созаемщик',
|
|
7119
|
-
params: { minItems: 1 }
|
|
7120
|
-
});
|
|
6381
|
+
validate(path.quantity, nonNegative());
|
|
6382
|
+
validate(path.balance, nonNegative({ message: 'Баланс не может быть отрицательным' }));
|
|
7121
6383
|
```
|
|
7122
6384
|
|
|
7123
|
-
_Source: src/core/validation/validators/
|
|
6385
|
+
_Source: src/core/validation/validators/non-negative.ts_
|
|
7124
6386
|
|
|
7125
|
-
###
|
|
6387
|
+
### nonZero
|
|
7126
6388
|
|
|
7127
6389
|
**Kind:** `function`
|
|
7128
6390
|
|
|
7129
|
-
|
|
7130
|
-
|
|
6391
|
+
Фабрика валидатора, проверяющего что число не равно нулю.
|
|
6392
|
+
|
|
6393
|
+
Пустые значения и не-числа пропускаются (используйте `required` и `isNumber`).
|
|
7131
6394
|
|
|
7132
6395
|
**Signature:**
|
|
7133
6396
|
```typescript
|
|
7134
|
-
export function
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
/** Минимальное значение (включительно) */
|
|
7138
|
-
min?: number;
|
|
7139
|
-
/** Максимальное значение (включительно) */
|
|
7140
|
-
max?: number;
|
|
7141
|
-
/** Требовать целое число */
|
|
7142
|
-
integer?: boolean;
|
|
7143
|
-
/** Значение должно быть кратно этому числу */
|
|
7144
|
-
multipleOf?: number;
|
|
7145
|
-
/** Разрешить отрицательные числа (по умолчанию true) */
|
|
7146
|
-
allowNegative?: boolean;
|
|
7147
|
-
/** Разрешить ноль (по умолчанию true) */
|
|
7148
|
-
allowZero?: boolean;
|
|
7149
|
-
}
|
|
7150
|
-
): void
|
|
6397
|
+
export function nonZero<TForm = unknown, TField extends number | undefined = number>(
|
|
6398
|
+
options?: ValidateOptions
|
|
6399
|
+
): Validator<TForm, TField>
|
|
7151
6400
|
```
|
|
7152
6401
|
|
|
7153
6402
|
**Examples:**
|
|
7154
6403
|
|
|
7155
6404
|
```typescript
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
number(path.percentage, { min: 0, max: 100, integer: true });
|
|
7159
|
-
number(path.rating, { min: 0, max: 5, multipleOf: 0.5 });
|
|
6405
|
+
validate(path.quantity, nonZero());
|
|
6406
|
+
validate(path.divisor, nonZero({ message: 'Не может быть нулём' }));
|
|
7160
6407
|
```
|
|
7161
6408
|
|
|
7162
|
-
_Source: src/core/validation/validators/
|
|
6409
|
+
_Source: src/core/validation/validators/non-zero.ts_
|
|
7163
6410
|
|
|
7164
6411
|
### ObservableForm
|
|
7165
6412
|
|
|
@@ -7204,128 +6451,63 @@ _Source: src/core/utils/form-observer.ts_
|
|
|
7204
6451
|
|
|
7205
6452
|
**Kind:** `function`
|
|
7206
6453
|
|
|
7207
|
-
|
|
6454
|
+
Фабрика валидатора, проверяющего что дата не в будущем.
|
|
7208
6455
|
|
|
7209
|
-
Пустые
|
|
6456
|
+
Пустые и невалидные даты пропускаются.
|
|
7210
6457
|
|
|
7211
6458
|
**Signature:**
|
|
7212
6459
|
```typescript
|
|
7213
|
-
export function pastDate<TForm, TField extends string | Date | undefined = string | Date>(
|
|
7214
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
6460
|
+
export function pastDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(
|
|
7215
6461
|
options?: ValidateOptions
|
|
7216
|
-
):
|
|
6462
|
+
): Validator<TForm, TField>
|
|
7217
6463
|
```
|
|
7218
6464
|
|
|
7219
|
-
**Parameters:**
|
|
7220
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
7221
|
-
- `options` — - Опции валидации (message, params)
|
|
7222
|
-
|
|
7223
6465
|
**Examples:**
|
|
7224
6466
|
|
|
7225
6467
|
```typescript
|
|
7226
|
-
|
|
7227
|
-
validationSchema: (path) => [
|
|
7228
|
-
pastDate(path.birthDate),
|
|
7229
|
-
]
|
|
7230
|
-
|
|
7231
|
-
// С кастомным сообщением
|
|
7232
|
-
pastDate(path.birthDate, { message: 'Дата рождения не может быть в будущем' })
|
|
7233
|
-
```
|
|
7234
|
-
|
|
7235
|
-
```typescript
|
|
7236
|
-
// Ошибка валидации
|
|
7237
|
-
{
|
|
7238
|
-
code: 'date_future',
|
|
7239
|
-
message: 'Дата не может быть в будущем',
|
|
7240
|
-
params: {}
|
|
7241
|
-
}
|
|
6468
|
+
validate(path.birthDate, pastDate({ message: 'Дата рождения не может быть в будущем' }));
|
|
7242
6469
|
```
|
|
7243
6470
|
|
|
7244
6471
|
_Source: src/core/validation/validators/past-date.ts_
|
|
7245
6472
|
|
|
7246
|
-
###
|
|
6473
|
+
### PathAwareSignal
|
|
7247
6474
|
|
|
7248
|
-
**Kind:** `
|
|
7249
|
-
|
|
7250
|
-
Сегмент пути к полю формы
|
|
6475
|
+
**Kind:** `type`
|
|
7251
6476
|
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
- `"items[0]" → { key: 'items', index: 0 }`
|
|
6477
|
+
Сигнал, который знает свой путь в модели (`'personalData.lastName'`).
|
|
6478
|
+
Используется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.
|
|
7255
6479
|
|
|
7256
6480
|
**Signature:**
|
|
7257
6481
|
```typescript
|
|
7258
|
-
export
|
|
7259
|
-
/**
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
key: string;
|
|
7263
|
-
|
|
7264
|
-
/**
|
|
7265
|
-
* Индекс в массиве (опционально)
|
|
7266
|
-
* Присутствует только для сегментов вида "items[0]"
|
|
7267
|
-
*/
|
|
7268
|
-
index?: number;
|
|
7269
|
-
}
|
|
7270
|
-
```
|
|
7271
|
-
|
|
7272
|
-
**Examples:**
|
|
7273
|
-
|
|
7274
|
-
```typescript
|
|
7275
|
-
// Путь "items[0].name" разбивается на:
|
|
7276
|
-
[
|
|
7277
|
-
{ key: 'items', index: 0 },
|
|
7278
|
-
{ key: 'name' }
|
|
7279
|
-
]
|
|
6482
|
+
export type PathAwareSignal<T> = Signal<T> & {
|
|
6483
|
+
/** Путь поля в модели (dot-нотация). На элементах массива включает индекс. */
|
|
6484
|
+
readonly __path: string;
|
|
6485
|
+
};
|
|
7280
6486
|
```
|
|
7281
6487
|
|
|
7282
|
-
_Source: src/core/
|
|
6488
|
+
_Source: src/core/model/types.ts_
|
|
7283
6489
|
|
|
7284
6490
|
### pattern
|
|
7285
6491
|
|
|
7286
6492
|
**Kind:** `function`
|
|
7287
6493
|
|
|
7288
|
-
|
|
6494
|
+
Фабрика валидатора регулярного выражения.
|
|
7289
6495
|
|
|
7290
|
-
Проверяет, что значение соответствует указанному регулярному выражению.
|
|
7291
6496
|
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
7292
6497
|
|
|
7293
6498
|
**Signature:**
|
|
7294
6499
|
```typescript
|
|
7295
|
-
export function pattern<TForm, TField extends string |
|
|
7296
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
6500
|
+
export function pattern<TForm = unknown, TField extends string | undefined = string>(
|
|
7297
6501
|
regex: RegExp,
|
|
7298
6502
|
options?: ValidateOptions
|
|
7299
|
-
):
|
|
6503
|
+
): Validator<TForm, TField>
|
|
7300
6504
|
```
|
|
7301
6505
|
|
|
7302
|
-
**Parameters:**
|
|
7303
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
7304
|
-
- `regex` — - Регулярное выражение для проверки
|
|
7305
|
-
- `options` — - Опции валидации (message, params)
|
|
7306
|
-
|
|
7307
6506
|
**Examples:**
|
|
7308
6507
|
|
|
7309
6508
|
```typescript
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
// Только цифры
|
|
7314
|
-
pattern(path.code, /^\d+$/)
|
|
7315
|
-
|
|
7316
|
-
// С кастомным сообщением
|
|
7317
|
-
pattern(path.username, /^[a-z0-9_]+$/i, {
|
|
7318
|
-
message: 'Только латинские буквы, цифры и подчёркивание'
|
|
7319
|
-
})
|
|
7320
|
-
```
|
|
7321
|
-
|
|
7322
|
-
```typescript
|
|
7323
|
-
// Ошибка валидации
|
|
7324
|
-
{
|
|
7325
|
-
code: 'pattern',
|
|
7326
|
-
message: 'Значение не соответствует требуемому формату',
|
|
7327
|
-
params: { pattern: '^[a-z]+$' }
|
|
7328
|
-
}
|
|
6509
|
+
validate(path.name, pattern(/^[а-яА-Яa-zA-Z]+$/));
|
|
6510
|
+
validate(path.code, pattern(/^\d+$/, { message: 'Только цифры' }));
|
|
7329
6511
|
```
|
|
7330
6512
|
|
|
7331
6513
|
_Source: src/core/validation/validators/pattern.ts_
|
|
@@ -7334,26 +6516,22 @@ _Source: src/core/validation/validators/pattern.ts_
|
|
|
7334
6516
|
|
|
7335
6517
|
**Kind:** `function`
|
|
7336
6518
|
|
|
7337
|
-
|
|
7338
|
-
|
|
6519
|
+
Фабрика валидатора номера телефона.
|
|
6520
|
+
|
|
6521
|
+
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
7339
6522
|
|
|
7340
6523
|
**Signature:**
|
|
7341
6524
|
```typescript
|
|
7342
|
-
export function phone<TForm, TField extends string |
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
/** Формат телефона */
|
|
7346
|
-
format?: PhoneFormat;
|
|
7347
|
-
}
|
|
7348
|
-
): void
|
|
6525
|
+
export function phone<TForm = unknown, TField extends string | undefined = string>(
|
|
6526
|
+
options?: PhoneValidatorOptions
|
|
6527
|
+
): Validator<TForm, TField>
|
|
7349
6528
|
```
|
|
7350
6529
|
|
|
7351
6530
|
**Examples:**
|
|
7352
6531
|
|
|
7353
6532
|
```typescript
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
phone(path.phoneNumber, { format: 'international', message: 'Неверный формат телефона' });
|
|
6533
|
+
validate(path.phone, phone());
|
|
6534
|
+
validate(path.phone, phone({ format: 'ru' }));
|
|
7357
6535
|
```
|
|
7358
6536
|
|
|
7359
6537
|
_Source: src/core/validation/validators/phone.ts_
|
|
@@ -7362,8 +6540,6 @@ _Source: src/core/validation/validators/phone.ts_
|
|
|
7362
6540
|
|
|
7363
6541
|
**Kind:** `type`
|
|
7364
6542
|
|
|
7365
|
-
Формат телефона для валидации
|
|
7366
|
-
|
|
7367
6543
|
**Signature:**
|
|
7368
6544
|
```typescript
|
|
7369
6545
|
export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
|
|
@@ -7371,6 +6547,19 @@ export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
|
|
|
7371
6547
|
|
|
7372
6548
|
_Source: src/core/validation/validators/phone.ts_
|
|
7373
6549
|
|
|
6550
|
+
### registerSignalNode
|
|
6551
|
+
|
|
6552
|
+
**Kind:** `function`
|
|
6553
|
+
|
|
6554
|
+
Связать сигнал модели с его FieldNode.
|
|
6555
|
+
|
|
6556
|
+
**Signature:**
|
|
6557
|
+
```typescript
|
|
6558
|
+
export function registerSignalNode(signal: Signal<any>, node: FormNode<any>): void
|
|
6559
|
+
```
|
|
6560
|
+
|
|
6561
|
+
_Source: src/core/utils/signal-node-registry.ts_
|
|
6562
|
+
|
|
7374
6563
|
### RegistryStack
|
|
7375
6564
|
|
|
7376
6565
|
**Kind:** `class`
|
|
@@ -7411,131 +6600,55 @@ _Source: src/core/utils/registry-stack.ts_
|
|
|
7411
6600
|
|
|
7412
6601
|
**Kind:** `function`
|
|
7413
6602
|
|
|
7414
|
-
|
|
6603
|
+
Фабрика валидатора обязательного поля.
|
|
6604
|
+
|
|
6605
|
+
Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
|
|
7415
6606
|
|
|
7416
|
-
Проверяет, что поле имеет непустое значение.
|
|
7417
6607
|
Пустыми считаются: `null`, `undefined`, `''` (пустая строка).
|
|
7418
6608
|
Для boolean полей требуется значение `true`.
|
|
7419
6609
|
|
|
7420
6610
|
**Signature:**
|
|
7421
6611
|
```typescript
|
|
7422
|
-
export function required<TForm, TField>(
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
): void
|
|
7426
|
-
```
|
|
7427
|
-
|
|
7428
|
-
**Parameters:**
|
|
7429
|
-
- `fieldPath` — - Путь к полю для валидации
|
|
7430
|
-
- `options` — - Опции валидации (message, params)
|
|
7431
|
-
|
|
7432
|
-
**Examples:**
|
|
7433
|
-
|
|
7434
|
-
```typescript
|
|
7435
|
-
// Базовое использование
|
|
7436
|
-
validationSchema: (path, { validate }) => [
|
|
7437
|
-
required(path.name),
|
|
7438
|
-
required(path.email),
|
|
7439
|
-
]
|
|
7440
|
-
|
|
7441
|
-
// С кастомным сообщением
|
|
7442
|
-
required(path.phone, { message: 'Укажите номер телефона' })
|
|
7443
|
-
|
|
7444
|
-
// Для checkbox (требует true)
|
|
7445
|
-
required(path.agreeToTerms, { message: 'Необходимо принять условия' })
|
|
7446
|
-
```
|
|
7447
|
-
|
|
7448
|
-
```typescript
|
|
7449
|
-
// Ошибка валидации
|
|
7450
|
-
{
|
|
7451
|
-
code: 'required',
|
|
7452
|
-
message: 'Поле обязательно для заполнения',
|
|
7453
|
-
params: {}
|
|
7454
|
-
}
|
|
7455
|
-
```
|
|
7456
|
-
|
|
7457
|
-
_Source: src/core/validation/validators/required.ts_
|
|
7458
|
-
|
|
7459
|
-
### resetWhen
|
|
7460
|
-
|
|
7461
|
-
**Kind:** `function`
|
|
7462
|
-
|
|
7463
|
-
Условный сброс поля при выполнении условия
|
|
7464
|
-
|
|
7465
|
-
**Signature:**
|
|
7466
|
-
```typescript
|
|
7467
|
-
export function resetWhen<TForm extends FormFields>(
|
|
7468
|
-
field: FieldPathNode<TForm, FormValue>,
|
|
7469
|
-
condition: (form: TForm) => boolean,
|
|
7470
|
-
options?: ResetWhenOptions & { debounce?: number }
|
|
7471
|
-
): void
|
|
6612
|
+
export function required<TForm = unknown, TField = unknown>(
|
|
6613
|
+
options?: ValidateOptions
|
|
6614
|
+
): Validator<TForm, TField>
|
|
7472
6615
|
```
|
|
7473
6616
|
|
|
7474
|
-
**Parameters:**
|
|
7475
|
-
- `field` — - Поле для сброса
|
|
7476
|
-
- `condition` — - Функция условия (true = reset)
|
|
7477
|
-
- `options` — - Опции (`resetValue`, `onlyIfDirty`, `debounce`)
|
|
7478
|
-
|
|
7479
6617
|
**Examples:**
|
|
7480
6618
|
|
|
7481
|
-
Сброс зависимого поля при смене типа платежа
|
|
7482
6619
|
```typescript
|
|
7483
|
-
import {
|
|
6620
|
+
import { validate } from '@reformer/core';
|
|
6621
|
+
import { required } from '@reformer/core/validators';
|
|
7484
6622
|
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
}
|
|
7489
|
-
|
|
7490
|
-
export const checkoutBehavior: BehaviorSchemaFn<CheckoutForm> = (path) => {
|
|
7491
|
-
// Когда выбрано НЕ card — обнуляем номер карты в пустую строку
|
|
7492
|
-
resetWhen(path.cardNumber, (form) => form.paymentType !== 'card', {
|
|
7493
|
-
resetValue: '',
|
|
7494
|
-
});
|
|
7495
|
-
};
|
|
6623
|
+
validate(path.email, required());
|
|
6624
|
+
validate(path.phone, required({ message: 'Укажите номер телефона' }));
|
|
6625
|
+
validate(path.agreeToTerms, required({ message: 'Необходимо принять условия' }));
|
|
7496
6626
|
```
|
|
7497
6627
|
|
|
7498
|
-
|
|
7499
|
-
```typescript
|
|
7500
|
-
import { resetWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
7501
|
-
|
|
7502
|
-
interface CarForm {
|
|
7503
|
-
loanType: 'mortgage' | 'car' | 'consumer';
|
|
7504
|
-
carPrice: number;
|
|
7505
|
-
}
|
|
7506
|
-
|
|
7507
|
-
export const carBehavior: BehaviorSchemaFn<CarForm> = (path) => {
|
|
7508
|
-
// Если пользователь не вводил carPrice — оставляем default из схемы.
|
|
7509
|
-
// Сбрасываем только если поле dirty (была пользовательская правка).
|
|
7510
|
-
resetWhen(path.carPrice, (form) => form.loanType !== 'car', {
|
|
7511
|
-
resetValue: 0,
|
|
7512
|
-
onlyIfDirty: true,
|
|
7513
|
-
});
|
|
7514
|
-
};
|
|
7515
|
-
```
|
|
6628
|
+
_Source: src/core/validation/validators/required.ts_
|
|
7516
6629
|
|
|
7517
|
-
|
|
7518
|
-
- [docs/llms/25-reset-when.md](../../../../docs/llms/25-reset-when.md)
|
|
6630
|
+
### resetWhen
|
|
7519
6631
|
|
|
7520
|
-
|
|
6632
|
+
**Kind:** `function`
|
|
7521
6633
|
|
|
7522
|
-
|
|
6634
|
+
Сброс значения поля к `resetValue` (по умолчанию `null`), когда `condition` истинно.
|
|
7523
6635
|
|
|
7524
|
-
**
|
|
6636
|
+
**Signature:**
|
|
6637
|
+
```typescript
|
|
6638
|
+
export function resetWhen<T>(
|
|
6639
|
+
target: Signal<T>,
|
|
6640
|
+
condition: () => boolean,
|
|
6641
|
+
options?: { resetValue?: T }
|
|
6642
|
+
): BehaviorCleanup
|
|
6643
|
+
```
|
|
7525
6644
|
|
|
7526
|
-
|
|
6645
|
+
**Examples:**
|
|
7527
6646
|
|
|
7528
|
-
**Signature:**
|
|
7529
6647
|
```typescript
|
|
7530
|
-
|
|
7531
|
-
/** Значение для сброса (по умолчанию null) */
|
|
7532
|
-
resetValue?: FormValue;
|
|
7533
|
-
/** Сбросить только если поле dirty */
|
|
7534
|
-
onlyIfDirty?: boolean;
|
|
7535
|
-
}
|
|
6648
|
+
resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue: '' });
|
|
7536
6649
|
```
|
|
7537
6650
|
|
|
7538
|
-
_Source: src/core/
|
|
6651
|
+
_Source: src/core/model/behaviors.ts_
|
|
7539
6652
|
|
|
7540
6653
|
### ResourceLoadResult
|
|
7541
6654
|
|
|
@@ -7554,82 +6667,24 @@ _Source: src/core/types/index.ts_
|
|
|
7554
6667
|
|
|
7555
6668
|
**Kind:** `function`
|
|
7556
6669
|
|
|
7557
|
-
|
|
6670
|
+
Вызывает `revalidate()` при изменении зависимостей (не на инициализации). Под M1 валидация
|
|
6671
|
+
on-demand (`validateFormModel`), поэтому ревалидация выражается явным колбэком.
|
|
7558
6672
|
|
|
7559
6673
|
**Signature:**
|
|
7560
6674
|
```typescript
|
|
7561
|
-
export function revalidateWhen
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
): void
|
|
6675
|
+
export function revalidateWhen(
|
|
6676
|
+
deps: ReadonlySignal<unknown>[],
|
|
6677
|
+
revalidate: () => void
|
|
6678
|
+
): BehaviorCleanup
|
|
7566
6679
|
```
|
|
7567
6680
|
|
|
7568
|
-
**Parameters:**
|
|
7569
|
-
- `target` — - Поле для перевалидации
|
|
7570
|
-
- `triggers` — - Поля-триггеры (НЕ должно содержать `target`)
|
|
7571
|
-
- `options` — - Опции (`debounce`)
|
|
7572
|
-
|
|
7573
6681
|
**Examples:**
|
|
7574
6682
|
|
|
7575
|
-
Парная перевалидация — confirmPassword при смене password
|
|
7576
|
-
```typescript
|
|
7577
|
-
import { revalidateWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
7578
|
-
import { equalTo } from '@reformer/core/validators';
|
|
7579
|
-
import type { FieldPath } from '@reformer/core';
|
|
7580
|
-
|
|
7581
|
-
interface RegistrationForm { password: string; confirmPassword: string }
|
|
7582
|
-
|
|
7583
|
-
export const validation = (path: FieldPath<RegistrationForm>) => {
|
|
7584
|
-
equalTo(path.confirmPassword, path.password, { message: 'Пароли не совпадают' });
|
|
7585
|
-
};
|
|
7586
|
-
|
|
7587
|
-
export const behavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
7588
|
-
// Если пользователь сначала ввёл confirm, потом меняет password —
|
|
7589
|
-
// без revalidateWhen ошибка confirmPassword останется устаревшей.
|
|
7590
|
-
revalidateWhen(path.confirmPassword, [path.password]);
|
|
7591
|
-
};
|
|
7592
|
-
```
|
|
7593
|
-
|
|
7594
|
-
Несколько триггеров + `debounce` для async-валидаторов
|
|
7595
|
-
```typescript
|
|
7596
|
-
import { revalidateWhen, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
7597
|
-
|
|
7598
|
-
interface MortgageForm {
|
|
7599
|
-
propertyValue: number;
|
|
7600
|
-
loanAmount: number;
|
|
7601
|
-
initialPayment: number; // правило: initialPayment >= propertyValue * 0.2 - loanAmount
|
|
7602
|
-
}
|
|
7603
|
-
|
|
7604
|
-
export const mortgageBehavior: BehaviorSchemaFn<MortgageForm> = (path) => {
|
|
7605
|
-
revalidateWhen(
|
|
7606
|
-
path.initialPayment,
|
|
7607
|
-
[path.propertyValue, path.loanAmount],
|
|
7608
|
-
{ debounce: 300 }, // не дёргаем сервер на каждый keystroke
|
|
7609
|
-
);
|
|
7610
|
-
};
|
|
7611
|
-
```
|
|
7612
|
-
|
|
7613
|
-
**See also:**
|
|
7614
|
-
- [docs/llms/27-revalidate-when.md](../../../../docs/llms/27-revalidate-when.md)
|
|
7615
|
-
|
|
7616
|
-
_Source: src/core/behavior/behaviors/revalidate-when.ts_
|
|
7617
|
-
|
|
7618
|
-
### RevalidateWhenOptions
|
|
7619
|
-
|
|
7620
|
-
**Kind:** `interface`
|
|
7621
|
-
|
|
7622
|
-
Опции для revalidateWhen
|
|
7623
|
-
|
|
7624
|
-
**Signature:**
|
|
7625
6683
|
```typescript
|
|
7626
|
-
|
|
7627
|
-
/** Debounce в мс */
|
|
7628
|
-
debounce?: number;
|
|
7629
|
-
}
|
|
6684
|
+
revalidateWhen([model.$.maxAmount], () => validateFormModel(model, schema));
|
|
7630
6685
|
```
|
|
7631
6686
|
|
|
7632
|
-
_Source: src/core/
|
|
6687
|
+
_Source: src/core/model/behaviors.ts_
|
|
7633
6688
|
|
|
7634
6689
|
### runOutsideEffect
|
|
7635
6690
|
|
|
@@ -7845,380 +6900,81 @@ _Source: src/core/utils/form-submitter.ts_
|
|
|
7845
6900
|
- Автоматически заменять существующие подписки
|
|
7846
6901
|
- Отслеживать количество активных подписок (для отладки)
|
|
7847
6902
|
|
|
7848
|
-
**Signature:**
|
|
7849
|
-
```typescript
|
|
7850
|
-
export class SubscriptionManager {
|
|
7851
|
-
/**
|
|
7852
|
-
* Хранилище подписок
|
|
7853
|
-
* Ключ: уникальный идентификатор подписки
|
|
7854
|
-
* Значение: функция отписки (dispose)
|
|
7855
|
-
*/ /* … */ }
|
|
7856
|
-
```
|
|
7857
|
-
|
|
7858
|
-
**Examples:**
|
|
7859
|
-
|
|
7860
|
-
```typescript
|
|
7861
|
-
class FieldNode {
|
|
7862
|
-
private subscriptions = new SubscriptionManager();
|
|
7863
|
-
|
|
7864
|
-
watch(callback: Function) {
|
|
7865
|
-
const dispose = effect(() => callback(this.value.value));
|
|
7866
|
-
return this.subscriptions.add('watch', dispose);
|
|
7867
|
-
}
|
|
7868
|
-
|
|
7869
|
-
dispose() {
|
|
7870
|
-
this.subscriptions.clear();
|
|
7871
|
-
}
|
|
7872
|
-
}
|
|
7873
|
-
```
|
|
7874
|
-
|
|
7875
|
-
_Source: src/core/utils/subscription-manager.ts_
|
|
7876
|
-
|
|
7877
|
-
### syncFields
|
|
7878
|
-
|
|
7879
|
-
**Kind:** `function`
|
|
7880
|
-
|
|
7881
|
-
Двусторонняя синхронизация двух полей
|
|
7882
|
-
|
|
7883
|
-
**Signature:**
|
|
7884
|
-
```typescript
|
|
7885
|
-
export function syncFields<TForm extends FormFields, T extends FormValue>(
|
|
7886
|
-
field1: FieldPathNode<TForm, T>,
|
|
7887
|
-
field2: FieldPathNode<TForm, T>,
|
|
7888
|
-
options?: SyncFieldsOptions<T>
|
|
7889
|
-
): void
|
|
7890
|
-
```
|
|
7891
|
-
|
|
7892
|
-
**Parameters:**
|
|
7893
|
-
- `field1` — - Первое поле
|
|
7894
|
-
- `field2` — - Второе поле
|
|
7895
|
-
- `options` — - Опции (`transform` асимметричен — применяется только field1 → field2; `debounce`)
|
|
7896
|
-
|
|
7897
|
-
**Examples:**
|
|
7898
|
-
|
|
7899
|
-
Базовый mirror двух текстовых полей
|
|
7900
|
-
```typescript
|
|
7901
|
-
import { syncFields, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
7902
|
-
|
|
7903
|
-
interface MirrorForm {
|
|
7904
|
-
syncField1: string;
|
|
7905
|
-
syncField2: string;
|
|
7906
|
-
}
|
|
7907
|
-
|
|
7908
|
-
export const mirrorBehavior: BehaviorSchemaFn<MirrorForm> = (path) => {
|
|
7909
|
-
syncFields(path.syncField1, path.syncField2);
|
|
7910
|
-
};
|
|
7911
|
-
```
|
|
7912
|
-
|
|
7913
|
-
С `transform` (асимметричный) и `debounce` для защиты от частых перезаписей
|
|
7914
|
-
```typescript
|
|
7915
|
-
import { syncFields, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
7916
|
-
|
|
7917
|
-
interface CodeForm {
|
|
7918
|
-
internalCode: string; // канонический формат
|
|
7919
|
-
displayCode: string; // показываем пользователю
|
|
7920
|
-
}
|
|
7921
|
-
|
|
7922
|
-
export const codeBehavior: BehaviorSchemaFn<CodeForm> = (path) => {
|
|
7923
|
-
// internalCode → displayCode: применяется toUpperCase
|
|
7924
|
-
// displayCode → internalCode: пишется как есть
|
|
7925
|
-
syncFields(path.internalCode, path.displayCode, {
|
|
7926
|
-
transform: (value) => (typeof value === 'string' ? value.toUpperCase() : value),
|
|
7927
|
-
debounce: 150, // сглаживает дёргание каретки
|
|
7928
|
-
});
|
|
7929
|
-
};
|
|
7930
|
-
```
|
|
7931
|
-
|
|
7932
|
-
**See also:**
|
|
7933
|
-
- [docs/llms/24-sync-fields.md](../../../../docs/llms/24-sync-fields.md)
|
|
7934
|
-
|
|
7935
|
-
_Source: src/core/behavior/behaviors/sync-fields.ts_
|
|
7936
|
-
|
|
7937
|
-
### SyncFieldsOptions
|
|
7938
|
-
|
|
7939
|
-
**Kind:** `interface`
|
|
7940
|
-
|
|
7941
|
-
Опции для syncFields
|
|
7942
|
-
|
|
7943
|
-
**Signature:**
|
|
7944
|
-
```typescript
|
|
7945
|
-
export interface SyncFieldsOptions<T> {
|
|
7946
|
-
/** Трансформация значения */
|
|
7947
|
-
transform?: (value: T) => T;
|
|
7948
|
-
|
|
7949
|
-
/** Debounce в мс */
|
|
7950
|
-
debounce?: number;
|
|
7951
|
-
}
|
|
7952
|
-
```
|
|
7953
|
-
|
|
7954
|
-
_Source: src/core/behavior/types.ts_
|
|
7955
|
-
|
|
7956
|
-
### toBehaviorFieldPath
|
|
7957
|
-
|
|
7958
|
-
**Kind:** `function`
|
|
7959
|
-
|
|
7960
|
-
Преобразовать FieldPath во вложенный путь для композиции behavior схем
|
|
7961
|
-
|
|
7962
|
-
Аналог toFieldPath из validation API.
|
|
7963
|
-
|
|
7964
|
-
**Signature:**
|
|
7965
|
-
```typescript
|
|
7966
|
-
export function toBehaviorFieldPath<TForm, TField>(
|
|
7967
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined
|
|
7968
|
-
): FieldPath<TField>
|
|
7969
|
-
```
|
|
7970
|
-
|
|
7971
|
-
**Parameters:**
|
|
7972
|
-
- `fieldPath` — - Поле для преобразования
|
|
7973
|
-
|
|
7974
|
-
**Returns:** Вложенный FieldPath
|
|
7975
|
-
|
|
7976
|
-
**Examples:**
|
|
7977
|
-
|
|
7978
|
-
```typescript
|
|
7979
|
-
// address-behavior.ts
|
|
7980
|
-
export const addressBehavior = (path: FieldPath<Address>) => {
|
|
7981
|
-
watchField(path.country, async (country, ctx) => {
|
|
7982
|
-
const regions = await fetchRegions(country);
|
|
7983
|
-
ctx.updateComponentProps(path.region, { options: regions });
|
|
7984
|
-
});
|
|
7985
|
-
};
|
|
7986
|
-
|
|
7987
|
-
// user-behavior.ts
|
|
7988
|
-
export const userBehavior = (path: FieldPath<User>) => {
|
|
7989
|
-
// Композиция: применяем addressBehavior к вложенному полю
|
|
7990
|
-
addressBehavior(toBehaviorFieldPath(path.address));
|
|
7991
|
-
};
|
|
7992
|
-
```
|
|
7993
|
-
|
|
7994
|
-
_Source: src/core/behavior/compose-behavior.ts_
|
|
7995
|
-
|
|
7996
|
-
### toFieldPath
|
|
7997
|
-
|
|
7998
|
-
**Kind:** `function`
|
|
7999
|
-
|
|
8000
|
-
Преобразовать FieldPathNode в FieldPath для переиспользования схем
|
|
8001
|
-
|
|
8002
|
-
Позволяет композировать validation schemas:
|
|
8003
|
-
|
|
8004
|
-
**Signature:**
|
|
8005
|
-
```typescript
|
|
8006
|
-
export function toFieldPath<T>(
|
|
8007
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8008
|
-
node: FieldPathNode<unknown, T, never> | FieldPathNode<any, T, any>
|
|
8009
|
-
): FieldPath<T>
|
|
8010
|
-
```
|
|
8011
|
-
|
|
8012
|
-
**Examples:**
|
|
8013
|
-
|
|
8014
|
-
```typescript
|
|
8015
|
-
const personalDataValidation = (path: FieldPath<PersonalData>) => {
|
|
8016
|
-
required(path.firstName, { message: 'Имя обязательно' });
|
|
8017
|
-
required(path.lastName, { message: 'Фамилия обязательна' });
|
|
8018
|
-
};
|
|
8019
|
-
|
|
8020
|
-
const mainValidation = (path: FieldPath<MyForm>) => {
|
|
8021
|
-
// Переиспользуем схему
|
|
8022
|
-
personalDataValidation(toFieldPath(path.personalData));
|
|
8023
|
-
required(path.email);
|
|
8024
|
-
};
|
|
8025
|
-
```
|
|
8026
|
-
|
|
8027
|
-
_Source: src/core/utils/field-path.ts_
|
|
8028
|
-
|
|
8029
|
-
### transformers
|
|
8030
|
-
|
|
8031
|
-
**Kind:** `const`
|
|
8032
|
-
|
|
8033
|
-
Готовые трансформеры для частых случаев. Все идемпотентны и безопасны для повторного применения.
|
|
8034
|
-
|
|
8035
|
-
**Signature:**
|
|
8036
|
-
```typescript
|
|
8037
|
-
export const transformers
|
|
8038
|
-
```
|
|
8039
|
-
|
|
8040
|
-
**Examples:**
|
|
8041
|
-
|
|
8042
|
-
Готовые трансформеры в схеме формы
|
|
8043
|
-
```typescript
|
|
8044
|
-
import { transformers, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
8045
|
-
|
|
8046
|
-
interface RegistrationForm {
|
|
8047
|
-
username: string;
|
|
8048
|
-
email: string;
|
|
8049
|
-
promoCode: string;
|
|
8050
|
-
inn: string;
|
|
8051
|
-
amount: number;
|
|
8052
|
-
}
|
|
8053
|
-
|
|
8054
|
-
export const behavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
8055
|
-
transformers.trim(path.username);
|
|
8056
|
-
transformers.toLowerCase(path.email);
|
|
8057
|
-
transformers.toUpperCase(path.promoCode);
|
|
8058
|
-
transformers.digitsOnly(path.inn);
|
|
8059
|
-
transformers.roundTo2(path.amount);
|
|
8060
|
-
};
|
|
8061
|
-
```
|
|
8062
|
-
|
|
8063
|
-
Композиция готовых трансформеров через createTransformer
|
|
8064
|
-
```typescript
|
|
8065
|
-
import { createTransformer, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
8066
|
-
|
|
8067
|
-
// trim + lowercase в одном трансформере (применяется как одна операция)
|
|
8068
|
-
const normalizeEmail = createTransformer<string>(
|
|
8069
|
-
(v) => (v ?? '').trim().toLowerCase(),
|
|
8070
|
-
);
|
|
8071
|
-
|
|
8072
|
-
interface ContactForm { email: string }
|
|
8073
|
-
|
|
8074
|
-
export const contactBehavior: BehaviorSchemaFn<ContactForm> = (path) => {
|
|
8075
|
-
normalizeEmail(path.email);
|
|
8076
|
-
};
|
|
8077
|
-
```
|
|
8078
|
-
|
|
8079
|
-
_Source: src/core/behavior/behaviors/transform-value.ts_
|
|
8080
|
-
|
|
8081
|
-
### transformValue
|
|
8082
|
-
|
|
8083
|
-
**Kind:** `function`
|
|
8084
|
-
|
|
8085
|
-
Трансформация значения поля при изменении
|
|
8086
|
-
Позволяет автоматически форматировать или преобразовывать значения
|
|
8087
|
-
|
|
8088
|
-
**Signature:**
|
|
8089
|
-
```typescript
|
|
8090
|
-
export function transformValue<TForm extends FormFields, TValue extends FormValue = FormValue>(
|
|
8091
|
-
field: FieldPathNode<TForm, TValue>,
|
|
8092
|
-
transformer: (value: TValue) => TValue,
|
|
8093
|
-
options?: TransformValueOptions & { debounce?: number }
|
|
8094
|
-
): void
|
|
8095
|
-
```
|
|
8096
|
-
|
|
8097
|
-
**Parameters:**
|
|
8098
|
-
- `field` — - Поле для трансформации
|
|
8099
|
-
- `transformer` — - Функция трансформации (ОБЯЗАТЕЛЬНО идемпотентная: f(f(x)) === f(x))
|
|
8100
|
-
- `options` — - Опции (`onUserChangeOnly`, `emitEvent`, `debounce`)
|
|
8101
|
-
|
|
8102
|
-
**Examples:**
|
|
8103
|
-
|
|
8104
|
-
Базовая нормализация — uppercase + trim email
|
|
8105
|
-
```typescript
|
|
8106
|
-
import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
8107
|
-
|
|
8108
|
-
interface RegistrationForm {
|
|
8109
|
-
promoCode: string;
|
|
8110
|
-
email: string;
|
|
8111
|
-
}
|
|
8112
|
-
|
|
8113
|
-
export const registrationBehavior: BehaviorSchemaFn<RegistrationForm> = (path) => {
|
|
8114
|
-
// Идемпотентно: toUpperCase(toUpperCase(x)) === toUpperCase(x) ✓
|
|
8115
|
-
transformValue(path.promoCode, (value) => (value ?? '').toUpperCase());
|
|
8116
|
-
transformValue(path.email, (value) => (value ?? '').trim().toLowerCase());
|
|
8117
|
-
};
|
|
8118
|
-
```
|
|
8119
|
-
|
|
8120
|
-
`onUserChangeOnly` + idempotent guard для не-тривиальных форматов
|
|
8121
|
-
```typescript
|
|
8122
|
-
import { transformValue, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
8123
|
-
|
|
8124
|
-
interface ProfileForm {
|
|
8125
|
-
inn: string; // ИНН — только цифры
|
|
8126
|
-
prefixedCode: string; // должен иметь префикс "ID-"
|
|
8127
|
-
}
|
|
8128
|
-
|
|
8129
|
-
export const profileBehavior: BehaviorSchemaFn<ProfileForm> = (path) => {
|
|
8130
|
-
// Цифры из ИНН: трансформер идемпотентный естественно
|
|
8131
|
-
transformValue(path.inn, (v) => (v ?? '').replace(/\D/g, ''));
|
|
8132
|
-
|
|
8133
|
-
// Префикс — ВАЖНО guard «уже преобразовано», иначе бесконечный цикл
|
|
8134
|
-
// f("ID-123") должно === "ID-123", а не "ID-ID-123"
|
|
8135
|
-
transformValue(
|
|
8136
|
-
path.prefixedCode,
|
|
8137
|
-
(v) => (v?.startsWith('ID-') ? v : `ID-${v ?? ''}`),
|
|
8138
|
-
{
|
|
8139
|
-
onUserChangeOnly: true, // не трогаем значение из patchValue/preload
|
|
8140
|
-
debounce: 200,
|
|
8141
|
-
},
|
|
8142
|
-
);
|
|
8143
|
-
};
|
|
8144
|
-
```
|
|
8145
|
-
|
|
8146
|
-
**See also:**
|
|
8147
|
-
- [docs/llms/26-transform-value.md](../../../../docs/llms/26-transform-value.md)
|
|
8148
|
-
|
|
8149
|
-
_Source: src/core/behavior/behaviors/transform-value.ts_
|
|
6903
|
+
**Signature:**
|
|
6904
|
+
```typescript
|
|
6905
|
+
export class SubscriptionManager {
|
|
6906
|
+
/**
|
|
6907
|
+
* Хранилище подписок
|
|
6908
|
+
* Ключ: уникальный идентификатор подписки
|
|
6909
|
+
* Значение: функция отписки (dispose)
|
|
6910
|
+
*/ /* … */ }
|
|
6911
|
+
```
|
|
8150
6912
|
|
|
8151
|
-
|
|
6913
|
+
**Examples:**
|
|
8152
6914
|
|
|
8153
|
-
|
|
6915
|
+
```typescript
|
|
6916
|
+
class FieldNode {
|
|
6917
|
+
private subscriptions = new SubscriptionManager();
|
|
8154
6918
|
|
|
8155
|
-
|
|
6919
|
+
watch(callback: Function) {
|
|
6920
|
+
const dispose = effect(() => callback(this.value.value));
|
|
6921
|
+
return this.subscriptions.add('watch', dispose);
|
|
6922
|
+
}
|
|
8156
6923
|
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
/** Трансформировать только при изменении пользователем (не программно) */
|
|
8161
|
-
onUserChangeOnly?: boolean;
|
|
8162
|
-
/** Триггерить событие изменения после трансформации */
|
|
8163
|
-
emitEvent?: boolean;
|
|
6924
|
+
dispose() {
|
|
6925
|
+
this.subscriptions.clear();
|
|
6926
|
+
}
|
|
8164
6927
|
}
|
|
8165
6928
|
```
|
|
8166
6929
|
|
|
8167
|
-
_Source: src/core/
|
|
6930
|
+
_Source: src/core/utils/subscription-manager.ts_
|
|
8168
6931
|
|
|
8169
|
-
###
|
|
6932
|
+
### syncFields
|
|
8170
6933
|
|
|
8171
|
-
**Kind:** `
|
|
6934
|
+
**Kind:** `function`
|
|
8172
6935
|
|
|
8173
|
-
|
|
8174
|
-
|
|
6936
|
+
Двусторонняя синхронизация двух полей (опционально с трансформом `a → b`).
|
|
6937
|
+
Сходимость обеспечивается peek-guard'ом; флаг предотвращает лишние круги.
|
|
8175
6938
|
|
|
8176
6939
|
**Signature:**
|
|
8177
6940
|
```typescript
|
|
8178
|
-
export
|
|
6941
|
+
export function syncFields<T>(
|
|
6942
|
+
a: Signal<T>,
|
|
6943
|
+
b: Signal<T>,
|
|
6944
|
+
options?: { transform?: (value: T) => T }
|
|
6945
|
+
): BehaviorCleanup
|
|
8179
6946
|
```
|
|
8180
6947
|
|
|
8181
6948
|
**Examples:**
|
|
8182
6949
|
|
|
8183
6950
|
```typescript
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
validateForm(form, (ctx) => {
|
|
8187
|
-
// ctx — экземпляр TreeValidationContextImpl
|
|
8188
|
-
if (ctx.form.startDate.value > ctx.form.endDate.value) {
|
|
8189
|
-
return [{ code: 'date-range', message: 'Дата начала позже даты окончания' }];
|
|
8190
|
-
}
|
|
8191
|
-
return [];
|
|
8192
|
-
});
|
|
6951
|
+
syncFields(model.$.field1, model.$.field2);
|
|
8193
6952
|
```
|
|
8194
6953
|
|
|
8195
|
-
_Source: src/core/
|
|
6954
|
+
_Source: src/core/model/behaviors.ts_
|
|
8196
6955
|
|
|
8197
|
-
###
|
|
6956
|
+
### transformValue
|
|
8198
6957
|
|
|
8199
|
-
**Kind:** `
|
|
6958
|
+
**Kind:** `function`
|
|
8200
6959
|
|
|
8201
|
-
|
|
6960
|
+
Трансформация значения поля (идемпотентная): при изменении пишет `transformer(value)` обратно.
|
|
6961
|
+
Запись отложена (`runOutsideEffect`) во избежание «Cycle detected» (эффект читает и пишет один сигнал).
|
|
8202
6962
|
|
|
8203
6963
|
**Signature:**
|
|
8204
6964
|
```typescript
|
|
8205
|
-
export
|
|
6965
|
+
export function transformValue<T>(
|
|
6966
|
+
target: Signal<T>,
|
|
6967
|
+
transformer: (value: T) => T
|
|
6968
|
+
): BehaviorCleanup
|
|
8206
6969
|
```
|
|
8207
6970
|
|
|
8208
6971
|
**Examples:**
|
|
8209
6972
|
|
|
8210
6973
|
```typescript
|
|
8211
|
-
|
|
8212
|
-
const password = ctx.form.password.value.value;
|
|
8213
|
-
const confirm = ctx.form.confirmPassword.value.value;
|
|
8214
|
-
if (password !== confirm) {
|
|
8215
|
-
return { code: 'mismatch', message: 'Passwords must match' };
|
|
8216
|
-
}
|
|
8217
|
-
return null;
|
|
8218
|
-
});
|
|
6974
|
+
transformValue(model.$.promoCode, (v) => (v ?? '').toUpperCase());
|
|
8219
6975
|
```
|
|
8220
6976
|
|
|
8221
|
-
_Source: src/core/
|
|
6977
|
+
_Source: src/core/model/behaviors.ts_
|
|
8222
6978
|
|
|
8223
6979
|
### uniqueId
|
|
8224
6980
|
|
|
@@ -8293,28 +7049,23 @@ _Source: src/core/types/index.ts_
|
|
|
8293
7049
|
|
|
8294
7050
|
**Kind:** `function`
|
|
8295
7051
|
|
|
8296
|
-
|
|
8297
|
-
|
|
7052
|
+
Фабрика валидатора URL.
|
|
7053
|
+
|
|
7054
|
+
Пустые значения пропускаются (используйте `required` для обязательности).
|
|
8298
7055
|
|
|
8299
7056
|
**Signature:**
|
|
8300
7057
|
```typescript
|
|
8301
|
-
export function url<TForm, TField extends string |
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
/** Требовать наличие протокола (http:// или https://) */
|
|
8305
|
-
requireProtocol?: boolean;
|
|
8306
|
-
/** Разрешенные протоколы */
|
|
8307
|
-
allowedProtocols?: string[];
|
|
8308
|
-
}
|
|
8309
|
-
): void
|
|
7058
|
+
export function url<TForm = unknown, TField extends string | undefined = string>(
|
|
7059
|
+
options?: UrlValidatorOptions
|
|
7060
|
+
): Validator<TForm, TField>
|
|
8310
7061
|
```
|
|
8311
7062
|
|
|
8312
7063
|
**Examples:**
|
|
8313
7064
|
|
|
8314
7065
|
```typescript
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
7066
|
+
validate(path.website, url());
|
|
7067
|
+
validate(path.website, url({ requireProtocol: true }));
|
|
7068
|
+
validate(path.website, url({ allowedProtocols: ['https'] }));
|
|
8318
7069
|
```
|
|
8319
7070
|
|
|
8320
7071
|
_Source: src/core/validation/validators/url.ts_
|
|
@@ -8767,130 +7518,11 @@ return (
|
|
|
8767
7518
|
|
|
8768
7519
|
_Source: src/hooks/useFormControlValue.ts_
|
|
8769
7520
|
|
|
8770
|
-
### useHiddenCondition
|
|
8771
|
-
|
|
8772
|
-
**Kind:** `function`
|
|
8773
|
-
|
|
8774
|
-
Хук для реактивной оценки функции hidden.
|
|
8775
|
-
|
|
8776
|
-
Подписывается на изменения формы через сигналы и возвращает текущее
|
|
8777
|
-
значение hidden-условия.
|
|
8778
|
-
|
|
8779
|
-
**Signature:**
|
|
8780
|
-
```typescript
|
|
8781
|
-
export function useHiddenCondition<T>(
|
|
8782
|
-
hiddenFn: HiddenFn<T> | undefined,
|
|
8783
|
-
form: FormProxy<T>,
|
|
8784
|
-
path: FieldPath<T>
|
|
8785
|
-
): boolean
|
|
8786
|
-
```
|
|
8787
|
-
|
|
8788
|
-
**Parameters:**
|
|
8789
|
-
- `hiddenFn` — - Функция, определяющая скрытие.
|
|
8790
|
-
- `form` — - {@link FormProxy} формы.
|
|
8791
|
-
- `path` — - Текущий {@link FieldPath}.
|
|
8792
|
-
|
|
8793
|
-
**Returns:** `true`, если элемент должен быть скрыт.
|
|
8794
|
-
|
|
8795
|
-
**Examples:**
|
|
8796
|
-
|
|
8797
|
-
```tsx
|
|
8798
|
-
import { useHiddenCondition } from '@reformer/core';
|
|
8799
|
-
|
|
8800
|
-
function MaybeHidden({ form, path, children }) {
|
|
8801
|
-
const hidden = useHiddenCondition((f) => !f.subscribeNewsletter.value, form, path);
|
|
8802
|
-
return hidden ? null : <>{children}</>;
|
|
8803
|
-
}
|
|
8804
|
-
```
|
|
8805
|
-
|
|
8806
|
-
_Source: src/hooks/useHiddenCondition.ts_
|
|
8807
|
-
|
|
8808
|
-
### validate
|
|
8809
|
-
|
|
8810
|
-
**Kind:** `function`
|
|
8811
|
-
|
|
8812
|
-
Зарегистрировать кастомный синхронный валидатор для поля
|
|
8813
|
-
Поддерживает опциональные поля
|
|
8814
|
-
|
|
8815
|
-
**Signature:**
|
|
8816
|
-
```typescript
|
|
8817
|
-
export function validate<TForm, TField>(
|
|
8818
|
-
fieldPath: FieldPathNode<TForm, TField> | undefined,
|
|
8819
|
-
validatorFn: ContextualValidatorFn<TForm, TField>,
|
|
8820
|
-
options?: ValidateOptions
|
|
8821
|
-
): void
|
|
8822
|
-
```
|
|
8823
|
-
|
|
8824
|
-
**Examples:**
|
|
8825
|
-
|
|
8826
|
-
```typescript
|
|
8827
|
-
validate(path.birthDate, (ctx: ValidationContext<TForm, TField>) => {
|
|
8828
|
-
const birthDate = new Date(ctx.value());
|
|
8829
|
-
const age = calculateAge(birthDate);
|
|
8830
|
-
|
|
8831
|
-
if (age < 18) {
|
|
8832
|
-
return {
|
|
8833
|
-
code: 'tooYoung',
|
|
8834
|
-
message: 'Заемщику должно быть не менее 18 лет',
|
|
8835
|
-
};
|
|
8836
|
-
}
|
|
8837
|
-
|
|
8838
|
-
return null;
|
|
8839
|
-
});
|
|
8840
|
-
```
|
|
8841
|
-
|
|
8842
|
-
_Source: src/core/validation/core/validate.ts_
|
|
8843
|
-
|
|
8844
|
-
### validateAsync
|
|
8845
|
-
|
|
8846
|
-
**Kind:** `function`
|
|
8847
|
-
|
|
8848
|
-
Зарегистрировать асинхронный валидатор для поля
|
|
8849
|
-
|
|
8850
|
-
**Signature:**
|
|
8851
|
-
```typescript
|
|
8852
|
-
export function validateAsync<TForm, TField>(
|
|
8853
|
-
fieldPath: FieldPathNode<TForm, TField>,
|
|
8854
|
-
validatorFn: ContextualAsyncValidatorFn<TForm, TField>,
|
|
8855
|
-
options?: ValidateAsyncOptions
|
|
8856
|
-
): void
|
|
8857
|
-
```
|
|
8858
|
-
|
|
8859
|
-
**Examples:**
|
|
8860
|
-
|
|
8861
|
-
```typescript
|
|
8862
|
-
validateAsync(
|
|
8863
|
-
path.inn,
|
|
8864
|
-
async (ctx: ValidationContext<TForm, TField>) => {
|
|
8865
|
-
const inn = ctx.value();
|
|
8866
|
-
if (!inn) return null;
|
|
8867
|
-
|
|
8868
|
-
const response = await fetch('/api/validate-inn', {
|
|
8869
|
-
method: 'POST',
|
|
8870
|
-
body: JSON.stringify({ inn }),
|
|
8871
|
-
});
|
|
8872
|
-
|
|
8873
|
-
const data = await response.json();
|
|
8874
|
-
if (!data.valid) {
|
|
8875
|
-
return {
|
|
8876
|
-
code: 'invalidInn',
|
|
8877
|
-
message: 'ИНН не найден в базе данных ФНС',
|
|
8878
|
-
};
|
|
8879
|
-
}
|
|
8880
|
-
|
|
8881
|
-
return null;
|
|
8882
|
-
},
|
|
8883
|
-
{ debounce: 1000 }
|
|
8884
|
-
);
|
|
8885
|
-
```
|
|
8886
|
-
|
|
8887
|
-
_Source: src/core/validation/core/validate-async.ts_
|
|
8888
|
-
|
|
8889
7521
|
### ValidateAsyncOptions
|
|
8890
7522
|
|
|
8891
7523
|
**Kind:** `interface`
|
|
8892
7524
|
|
|
8893
|
-
Опции для функции validateAsync
|
|
7525
|
+
Опции для функции validateAsync.
|
|
8894
7526
|
|
|
8895
7527
|
**Signature:**
|
|
8896
7528
|
```typescript
|
|
@@ -8902,111 +7534,65 @@ export interface ValidateAsyncOptions extends ValidateOptions {
|
|
|
8902
7534
|
|
|
8903
7535
|
_Source: src/core/types/validation-schema.ts_
|
|
8904
7536
|
|
|
8905
|
-
###
|
|
7537
|
+
### validateFormModel
|
|
8906
7538
|
|
|
8907
7539
|
**Kind:** `function`
|
|
8908
7540
|
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
из схемы и очищает контекст без сохранения в реестр.
|
|
7541
|
+
In-form валидация: прогоняет {@link validateModel} и роутит ошибки в ноды формы
|
|
7542
|
+
(через реестр сигнал→нода): `node.setErrors(errors[path] ?? [])` — заодно очищает прошедшие поля
|
|
7543
|
+
И поля выключенных веток (`{ when, children }` с ложным условием). Дерево обходится ОДИН раз.
|
|
8913
7544
|
|
|
8914
7545
|
**Signature:**
|
|
8915
7546
|
```typescript
|
|
8916
|
-
export async function
|
|
8917
|
-
|
|
8918
|
-
schema:
|
|
8919
|
-
): Promise<
|
|
7547
|
+
export async function validateFormModel<T>(
|
|
7548
|
+
model: FormModel<T>,
|
|
7549
|
+
schema: unknown
|
|
7550
|
+
): Promise<ModelValidationResult>
|
|
8920
7551
|
```
|
|
8921
7552
|
|
|
8922
|
-
|
|
8923
|
-
- `form` — - GroupNode для валидации
|
|
8924
|
-
- `schema` — - Схема валидации (ValidationSchemaFn)
|
|
8925
|
-
|
|
8926
|
-
**Returns:** Promise<boolean> - `true` если форма валидна, `false` если есть ошибки
|
|
8927
|
-
|
|
8928
|
-
**Examples:**
|
|
7553
|
+
_Source: src/core/model/validate-model.ts_
|
|
8929
7554
|
|
|
8930
|
-
|
|
8931
|
-
```typescript
|
|
8932
|
-
const goToNextStep = async () => {
|
|
8933
|
-
const isValid = await validateForm(form, step1LoanValidation);
|
|
7555
|
+
### validateModel
|
|
8934
7556
|
|
|
8935
|
-
|
|
8936
|
-
form.markAsTouched(); // Показать ошибки
|
|
8937
|
-
return false;
|
|
8938
|
-
}
|
|
7557
|
+
**Kind:** `function`
|
|
8939
7558
|
|
|
8940
|
-
|
|
8941
|
-
return true;
|
|
8942
|
-
};
|
|
8943
|
-
```
|
|
7559
|
+
Headless-валидация данных (sync + async). Работает без UI/нод.
|
|
8944
7560
|
|
|
8945
|
-
|
|
7561
|
+
**Signature:**
|
|
8946
7562
|
```typescript
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
await form.submit(onSubmit);
|
|
8952
|
-
}
|
|
8953
|
-
};
|
|
7563
|
+
export async function validateModel<T>(
|
|
7564
|
+
model: FormModel<T>,
|
|
7565
|
+
schema: unknown
|
|
7566
|
+
): Promise<ModelValidationResult>
|
|
8954
7567
|
```
|
|
8955
7568
|
|
|
8956
|
-
|
|
8957
|
-
```typescript
|
|
8958
|
-
const schema = isBusinessAccount
|
|
8959
|
-
? businessValidation
|
|
8960
|
-
: personalValidation;
|
|
7569
|
+
**Examples:**
|
|
8961
7570
|
|
|
8962
|
-
|
|
7571
|
+
```typescript
|
|
7572
|
+
const res = await validateModel(model, schema);
|
|
7573
|
+
if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
|
|
8963
7574
|
```
|
|
8964
7575
|
|
|
8965
|
-
_Source: src/core/
|
|
7576
|
+
_Source: src/core/model/validate-model.ts_
|
|
8966
7577
|
|
|
8967
|
-
###
|
|
7578
|
+
### validateModelSync
|
|
8968
7579
|
|
|
8969
7580
|
**Kind:** `function`
|
|
8970
7581
|
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
Регистрирует схему валидации, которая будет автоматически применяться
|
|
8974
|
-
к каждому элементу ArrayNode (как существующим, так и новым).
|
|
7582
|
+
Синхронная headless-валидация данных. Асинхронные валидаторы пропускаются.
|
|
8975
7583
|
|
|
8976
7584
|
**Signature:**
|
|
8977
7585
|
```typescript
|
|
8978
|
-
export function
|
|
8979
|
-
fieldPath: FieldPathNode<TForm, TItem[] | undefined> | undefined,
|
|
8980
|
-
itemSchemaFn: ValidationSchemaFn<TItem>
|
|
8981
|
-
): void
|
|
8982
|
-
```
|
|
8983
|
-
|
|
8984
|
-
**Parameters:**
|
|
8985
|
-
- `fieldPath` — - Поле-массив для валидации элементов
|
|
8986
|
-
- `itemSchemaFn` — - Validation schema для одного элемента
|
|
8987
|
-
|
|
8988
|
-
**Examples:**
|
|
8989
|
-
|
|
8990
|
-
```typescript
|
|
8991
|
-
import { propertyValidation } from './property-validation';
|
|
8992
|
-
|
|
8993
|
-
// В additionalValidation
|
|
8994
|
-
applyWhen(path.hasProperty, (value) => value === true, (path) => {
|
|
8995
|
-
// Проверка что массив не пустой
|
|
8996
|
-
notEmpty(path.properties, { message: 'Добавьте хотя бы один объект имущества' });
|
|
8997
|
-
|
|
8998
|
-
// Валидация каждого элемента
|
|
8999
|
-
validateItems(path.properties, propertyValidation);
|
|
9000
|
-
});
|
|
7586
|
+
export function validateModelSync<T>(model: FormModel<T>, schema: unknown): ModelValidationResult
|
|
9001
7587
|
```
|
|
9002
7588
|
|
|
9003
|
-
_Source: src/core/
|
|
7589
|
+
_Source: src/core/model/validate-model.ts_
|
|
9004
7590
|
|
|
9005
7591
|
### ValidateOptions
|
|
9006
7592
|
|
|
9007
7593
|
**Kind:** `interface`
|
|
9008
7594
|
|
|
9009
|
-
Опции для функции validate
|
|
7595
|
+
Опции для функции validate.
|
|
9010
7596
|
|
|
9011
7597
|
**Signature:**
|
|
9012
7598
|
```typescript
|
|
@@ -9020,88 +7606,6 @@ export interface ValidateOptions {
|
|
|
9020
7606
|
|
|
9021
7607
|
_Source: src/core/types/validation-schema.ts_
|
|
9022
7608
|
|
|
9023
|
-
### validateTree
|
|
9024
|
-
|
|
9025
|
-
**Kind:** `function`
|
|
9026
|
-
|
|
9027
|
-
Зарегистрировать cross-field валидатор
|
|
9028
|
-
|
|
9029
|
-
Используется для валидации, которая зависит от нескольких полей
|
|
9030
|
-
|
|
9031
|
-
**Signature:**
|
|
9032
|
-
```typescript
|
|
9033
|
-
export function validateTree<TForm>(
|
|
9034
|
-
validatorFn: TreeValidatorFn<TForm>,
|
|
9035
|
-
options?: ValidateTreeOptions
|
|
9036
|
-
): void
|
|
9037
|
-
```
|
|
9038
|
-
|
|
9039
|
-
**Examples:**
|
|
9040
|
-
|
|
9041
|
-
```typescript
|
|
9042
|
-
// Явная типизация ctx для избежания implicit any
|
|
9043
|
-
validateTree(
|
|
9044
|
-
(ctx: { form: MyForm }) => {
|
|
9045
|
-
if (ctx.form.initialPayment && ctx.form.propertyValue) {
|
|
9046
|
-
if (ctx.form.initialPayment > ctx.form.propertyValue) {
|
|
9047
|
-
return {
|
|
9048
|
-
code: 'initialPaymentTooHigh',
|
|
9049
|
-
message: 'Первоначальный взнос не может превышать стоимость',
|
|
9050
|
-
};
|
|
9051
|
-
}
|
|
9052
|
-
}
|
|
9053
|
-
return null;
|
|
9054
|
-
},
|
|
9055
|
-
{ targetField: 'initialPayment' }
|
|
9056
|
-
);
|
|
9057
|
-
```
|
|
9058
|
-
|
|
9059
|
-
_Source: src/core/validation/core/validate-tree.ts_
|
|
9060
|
-
|
|
9061
|
-
### ValidateTreeOptions
|
|
9062
|
-
|
|
9063
|
-
**Kind:** `interface`
|
|
9064
|
-
|
|
9065
|
-
Опции для функции validateTree
|
|
9066
|
-
|
|
9067
|
-
**Signature:**
|
|
9068
|
-
```typescript
|
|
9069
|
-
export interface ValidateTreeOptions {
|
|
9070
|
-
/** Поле, к которому привязать ошибку */
|
|
9071
|
-
targetField?: string;
|
|
9072
|
-
}
|
|
9073
|
-
```
|
|
9074
|
-
|
|
9075
|
-
_Source: src/core/types/validation-schema.ts_
|
|
9076
|
-
|
|
9077
|
-
### ValidationContextImpl
|
|
9078
|
-
|
|
9079
|
-
**Kind:** `class`
|
|
9080
|
-
|
|
9081
|
-
Контекст валидации одного поля. Создаётся фреймворком и передаётся в валидаторы
|
|
9082
|
-
(`required`, `validate`, …) — напрямую инстанцировать не нужно.
|
|
9083
|
-
|
|
9084
|
-
**Signature:**
|
|
9085
|
-
```typescript
|
|
9086
|
-
export class ValidationContextImpl<TForm, TField> extends BaseValidationContext<TForm> { /* … */ }
|
|
9087
|
-
```
|
|
9088
|
-
|
|
9089
|
-
**Examples:**
|
|
9090
|
-
|
|
9091
|
-
```typescript
|
|
9092
|
-
import { validate } from '@reformer/core/validators';
|
|
9093
|
-
|
|
9094
|
-
validate(path.password, (value, ctx) => {
|
|
9095
|
-
// ctx — экземпляр ValidationContextImpl, доступ к ctx.form для cross-field логики
|
|
9096
|
-
if (value !== ctx.form.confirmPassword.value) {
|
|
9097
|
-
return { code: 'mismatch', message: 'Пароли не совпадают' };
|
|
9098
|
-
}
|
|
9099
|
-
return null;
|
|
9100
|
-
});
|
|
9101
|
-
```
|
|
9102
|
-
|
|
9103
|
-
_Source: src/core/validation/validation-context.ts_
|
|
9104
|
-
|
|
9105
7609
|
### ValidationError
|
|
9106
7610
|
|
|
9107
7611
|
**Kind:** `interface`
|
|
@@ -9121,52 +7625,32 @@ export interface ValidationError {
|
|
|
9121
7625
|
|
|
9122
7626
|
_Source: src/core/types/index.ts_
|
|
9123
7627
|
|
|
9124
|
-
###
|
|
9125
|
-
|
|
9126
|
-
**Kind:** `class`
|
|
7628
|
+
### Validator
|
|
9127
7629
|
|
|
9128
|
-
|
|
7630
|
+
**Kind:** `type`
|
|
9129
7631
|
|
|
9130
|
-
|
|
9131
|
-
Устраняет race conditions и изолирует формы друг от друга.
|
|
7632
|
+
Чистый синхронный валидатор поля.
|
|
9132
7633
|
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
- Template methods begin/end registration
|
|
7634
|
+
Принимает значение поля, прокси текущего поля (control) и прокси корня формы (root).
|
|
7635
|
+
Возвращает ValidationError либо null. Не знает про реестр валидации.
|
|
9136
7636
|
|
|
9137
7637
|
**Signature:**
|
|
9138
7638
|
```typescript
|
|
9139
|
-
export
|
|
9140
|
-
|
|
7639
|
+
export type Validator<TForm, TField> = (
|
|
7640
|
+
value: TField,
|
|
7641
|
+
control: FormProxy<TField>,
|
|
7642
|
+
root: FormProxy<TForm>
|
|
7643
|
+
) => ValidationError | null;
|
|
9141
7644
|
```
|
|
9142
7645
|
|
|
9143
7646
|
**Examples:**
|
|
9144
7647
|
|
|
9145
7648
|
```typescript
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
schemaFn(createFieldPath(this)); // Uses getCurrent()
|
|
9152
|
-
this.validationRegistry.endRegistration(this); // Pops from global stack
|
|
9153
|
-
}
|
|
9154
|
-
}
|
|
9155
|
-
```
|
|
9156
|
-
|
|
9157
|
-
_Source: src/core/validation/validation-registry.ts_
|
|
9158
|
-
|
|
9159
|
-
### ValidationSchemaFn
|
|
9160
|
-
|
|
9161
|
-
**Kind:** `type`
|
|
9162
|
-
|
|
9163
|
-
Функция validation schema
|
|
9164
|
-
|
|
9165
|
-
Принимает FieldPath и определяет все правила валидации для формы
|
|
9166
|
-
|
|
9167
|
-
**Signature:**
|
|
9168
|
-
```typescript
|
|
9169
|
-
export type ValidationSchemaFn<T> = (path: FieldPath<T>) => void;
|
|
7649
|
+
const isAdult: Validator<MyForm, number> = (value, control, root) => {
|
|
7650
|
+
if (value < 18) return { code: 'tooYoung', message: '18+' };
|
|
7651
|
+
return null;
|
|
7652
|
+
};
|
|
7653
|
+
validate(path.age, isAdult);
|
|
9170
7654
|
```
|
|
9171
7655
|
|
|
9172
7656
|
_Source: src/core/types/validation-schema.ts_
|
|
@@ -9184,141 +7668,32 @@ export type ValidatorFn<T = FormValue> = (value: T) => ValidationError | null;
|
|
|
9184
7668
|
|
|
9185
7669
|
_Source: src/core/types/index.ts_
|
|
9186
7670
|
|
|
9187
|
-
### ValidatorRegistration
|
|
9188
|
-
|
|
9189
|
-
**Kind:** `interface`
|
|
9190
|
-
|
|
9191
|
-
Регистрация валидатора в системе
|
|
9192
|
-
|
|
9193
|
-
**Signature:**
|
|
9194
|
-
```typescript
|
|
9195
|
-
export interface ValidatorRegistration {
|
|
9196
|
-
fieldPath: string;
|
|
9197
|
-
type: 'sync' | 'async' | 'tree';
|
|
9198
|
-
validator:
|
|
9199
|
-
| ContextualValidatorFn<unknown, unknown>
|
|
9200
|
-
| ContextualAsyncValidatorFn<unknown, unknown>
|
|
9201
|
-
| TreeValidatorFn<unknown>;
|
|
9202
|
-
options?: ValidateOptions | ValidateAsyncOptions | ValidateTreeOptions;
|
|
9203
|
-
condition?: {
|
|
9204
|
-
fieldPath: string;
|
|
9205
|
-
conditionFn: ConditionFn<unknown>;
|
|
9206
|
-
};
|
|
9207
|
-
}
|
|
9208
|
-
```
|
|
9209
|
-
|
|
9210
|
-
_Source: src/core/types/validation-schema.ts_
|
|
9211
|
-
|
|
9212
7671
|
### watchField
|
|
9213
7672
|
|
|
9214
7673
|
**Kind:** `function`
|
|
9215
7674
|
|
|
9216
|
-
|
|
7675
|
+
Реакция на изменение поля: вызывает `cb(value)` при каждом изменении (по умолчанию без вызова на
|
|
7676
|
+
инициализации; `immediate: true` — вызвать сразу).
|
|
9217
7677
|
|
|
9218
7678
|
**Signature:**
|
|
9219
7679
|
```typescript
|
|
9220
|
-
export function watchField<
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
options?:
|
|
9224
|
-
):
|
|
7680
|
+
export function watchField<T>(
|
|
7681
|
+
source: ReadonlySignal<T>,
|
|
7682
|
+
cb: (value: T) => void,
|
|
7683
|
+
options?: { immediate?: boolean }
|
|
7684
|
+
): BehaviorCleanup
|
|
9225
7685
|
```
|
|
9226
7686
|
|
|
9227
|
-
**Parameters:**
|
|
9228
|
-
- `field` — - Поле для отслеживания
|
|
9229
|
-
- `callback` — - Функция обратного вызова
|
|
9230
|
-
- `options` — - Опции (`debounce`, `immediate`)
|
|
9231
|
-
|
|
9232
7687
|
**Examples:**
|
|
9233
7688
|
|
|
9234
|
-
Async loader with try/catch + guard + debounce
|
|
9235
|
-
```typescript
|
|
9236
|
-
import { watchField, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
9237
|
-
|
|
9238
|
-
interface AddressForm { region: string; city: string }
|
|
9239
|
-
|
|
9240
|
-
export const addressBehavior: BehaviorSchemaFn<AddressForm> = (path) => {
|
|
9241
|
-
watchField(
|
|
9242
|
-
path.region,
|
|
9243
|
-
async (region, ctx) => {
|
|
9244
|
-
if (!region) {
|
|
9245
|
-
ctx.form.city.updateComponentProps({ options: [] });
|
|
9246
|
-
return; // guard: пустое значение не триггерит fetch
|
|
9247
|
-
}
|
|
9248
|
-
try {
|
|
9249
|
-
const { data: cities } = await fetchCities(region);
|
|
9250
|
-
ctx.form.city.updateComponentProps({ options: cities });
|
|
9251
|
-
} catch (error) {
|
|
9252
|
-
console.error('[addressBehavior] failed to load cities:', error);
|
|
9253
|
-
ctx.form.city.updateComponentProps({ options: [] });
|
|
9254
|
-
}
|
|
9255
|
-
},
|
|
9256
|
-
{ immediate: false, debounce: 300 }, // обязательные опции для async
|
|
9257
|
-
);
|
|
9258
|
-
};
|
|
9259
|
-
```
|
|
9260
|
-
|
|
9261
|
-
Sync handler с консолидацией нескольких зависимостей в один watcher
|
|
9262
|
-
```typescript
|
|
9263
|
-
import { watchField, type BehaviorSchemaFn } from '@reformer/core/behaviors';
|
|
9264
|
-
|
|
9265
|
-
interface InsuranceForm {
|
|
9266
|
-
insuranceType: 'casco' | 'osago' | 'property' | '';
|
|
9267
|
-
vehicleVin: string;
|
|
9268
|
-
propertyType: string;
|
|
9269
|
-
}
|
|
9270
|
-
|
|
9271
|
-
export const insuranceBehavior: BehaviorSchemaFn<InsuranceForm> = (path) => {
|
|
9272
|
-
// ОДИН watchField на trigger-поле — несколько watcher'ов на одно поле = "Cycle detected"
|
|
9273
|
-
watchField(
|
|
9274
|
-
path.insuranceType,
|
|
9275
|
-
(type, ctx) => {
|
|
9276
|
-
const isVehicle = type === 'casco' || type === 'osago';
|
|
9277
|
-
const isProperty = type === 'property';
|
|
9278
|
-
|
|
9279
|
-
// Guard — ставим только если состояние реально меняется
|
|
9280
|
-
if (isVehicle) {
|
|
9281
|
-
if (ctx.form.vehicleVin.disabled.value) ctx.form.vehicleVin.enable();
|
|
9282
|
-
} else {
|
|
9283
|
-
if (!ctx.form.vehicleVin.disabled.value) ctx.form.vehicleVin.disable();
|
|
9284
|
-
if (ctx.form.vehicleVin.getValue() !== '') ctx.form.vehicleVin.setValue('');
|
|
9285
|
-
}
|
|
9286
|
-
|
|
9287
|
-
if (isProperty) {
|
|
9288
|
-
if (ctx.form.propertyType.disabled.value) ctx.form.propertyType.enable();
|
|
9289
|
-
} else {
|
|
9290
|
-
if (!ctx.form.propertyType.disabled.value) ctx.form.propertyType.disable();
|
|
9291
|
-
if (ctx.form.propertyType.getValue() !== '') ctx.form.propertyType.setValue('');
|
|
9292
|
-
}
|
|
9293
|
-
},
|
|
9294
|
-
{ immediate: false }, // CRITICAL: предотвращает запуск во время инициализации
|
|
9295
|
-
);
|
|
9296
|
-
};
|
|
9297
|
-
```
|
|
9298
|
-
|
|
9299
|
-
**See also:**
|
|
9300
|
-
- [docs/llms/22-cycle-detection.md](../../../../docs/llms/22-cycle-detection.md)
|
|
9301
|
-
|
|
9302
|
-
_Source: src/core/behavior/behaviors/watch-field.ts_
|
|
9303
|
-
|
|
9304
|
-
### WatchFieldOptions
|
|
9305
|
-
|
|
9306
|
-
**Kind:** `interface`
|
|
9307
|
-
|
|
9308
|
-
Опции для watchField
|
|
9309
|
-
|
|
9310
|
-
**Signature:**
|
|
9311
7689
|
```typescript
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
/** Вызвать сразу при инициализации */
|
|
9317
|
-
immediate?: boolean;
|
|
9318
|
-
}
|
|
7690
|
+
watchField(model.$.country, async (country) => {
|
|
7691
|
+
model.city = '';
|
|
7692
|
+
// ... загрузить города
|
|
7693
|
+
});
|
|
9319
7694
|
```
|
|
9320
7695
|
|
|
9321
|
-
_Source: src/core/
|
|
7696
|
+
_Source: src/core/model/behaviors.ts_
|
|
9322
7697
|
|
|
9323
7698
|
### WithBehaviorSchema
|
|
9324
7699
|
|