@reformer/core 10.2.0 → 11.0.0-beta.2
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/dist/behaviors.js +134 -134
- package/dist/form/form-submitter.d.ts +0 -4
- package/dist/form/hooks/types.d.ts +3 -3
- package/dist/form/index.d.ts +0 -3
- package/dist/form/nodes/array-node.d.ts +0 -84
- package/dist/form/nodes/form-node.d.ts +0 -14
- package/dist/form/nodes/group-node.d.ts +0 -4
- package/dist/form/status-machine.d.ts +0 -38
- package/dist/form/types/contracts.d.ts +0 -6
- package/dist/form/types/deep-schema.d.ts +0 -9
- package/dist/form/types/index.d.ts +4 -38
- package/dist/form/types/schema-node.d.ts +13 -16
- package/dist/form/types/validation-schema.d.ts +0 -25
- package/dist/form/unique-id.d.ts +3 -11
- package/dist/form/validators.d.ts +8 -3
- package/dist/form-model-DhW2HZOB.js +323 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +448 -636
- package/dist/runtime-token.d.ts +23 -0
- package/dist/state/form-model.d.ts +14 -0
- package/dist/state/index.d.ts +2 -1
- package/dist/state/types.d.ts +39 -11
- package/dist/state.js +18 -18
- package/dist/validation.js +1 -1
- package/dist/validators.d.ts +0 -4
- package/dist/validators.js +88 -122
- package/docs/llms/01-api-reference.md +63 -0
- package/docs/llms/02-quick-start.md +143 -0
- package/docs/llms/03-api-signatures.md +273 -0
- package/docs/llms/04-common-patterns.md +202 -0
- package/docs/llms/05-common-mistakes.md +167 -0
- package/docs/llms/06-troubleshooting.md +11 -0
- package/docs/llms/07-complete-import.md +99 -0
- package/docs/llms/08-form-types.md +47 -0
- package/docs/llms/09-formschema.md +143 -0
- package/docs/llms/10-arrays.md +161 -0
- package/docs/llms/11-async-watchfield.md +53 -0
- package/docs/llms/12-array-cleanup.md +31 -0
- package/docs/llms/13-multi-step.md +91 -0
- package/docs/llms/14-extended-mistakes.md +98 -0
- package/docs/llms/15-project-structure.md +125 -0
- package/docs/llms/16-ui-components.md +189 -0
- package/docs/llms/17-nonexistent-api.md +93 -0
- package/docs/llms/18-conditional-fields.md +183 -0
- package/docs/llms/19-reading-values.md +88 -0
- package/docs/llms/20-compute-vs-watch.md +147 -0
- package/docs/llms/21-array-operations.md +113 -0
- package/docs/llms/22-cycle-detection.md +86 -0
- package/docs/llms/23-copy-from.md +141 -0
- package/docs/llms/24-sync-fields.md +114 -0
- package/docs/llms/25-reset-when.md +115 -0
- package/docs/llms/26-transform-value.md +136 -0
- package/docs/llms/27-revalidate-when.md +132 -0
- package/docs/llms/28-submit-and-reset.md +259 -0
- package/docs/llms/29-async-preload.md +193 -0
- package/docs/llms/30-type-safety-recipes.md +159 -0
- package/docs/llms/31-async-validator-debounce.md +135 -0
- package/docs/llms/32-async-options-loading.md +108 -0
- package/docs/llms/33-validation-strategy.md +193 -0
- package/llms.txt +219 -1533
- package/package.json +33 -10
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form-model-DYCzGGKF.js +0 -286
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Штамп копии рантайма ядра.
|
|
3
|
+
*
|
|
4
|
+
* Единственное назначение — быть объектом, уникальным в пределах ОДНОЙ копии модуля. Две
|
|
5
|
+
* независимо загруженные копии `@reformer/core` дадут два разных объекта, и по их несовпадению
|
|
6
|
+
* guard распознаёт дубль рантайма.
|
|
7
|
+
*
|
|
8
|
+
* Почему именно объект, а не строка с версией: две копии ОДНОЙ версии ломают всё ровно так же,
|
|
9
|
+
* как две копии разных версий, — раздваиваются модульные `WeakMap` (`derived-registry`,
|
|
10
|
+
* `signal-node-registry`) и перестают работать проверки `instanceof Signal`. Сравнение версий
|
|
11
|
+
* такой случай пропустило бы.
|
|
12
|
+
*
|
|
13
|
+
* @see `@reformer/form-registry/guard`
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { CORE_RUNTIME_TOKEN } from '@reformer/core';
|
|
18
|
+
* import { stampRuntime } from '@reformer/form-registry/guard';
|
|
19
|
+
*
|
|
20
|
+
* stampRuntime('@reformer/core', CORE_RUNTIME_TOKEN);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const CORE_RUNTIME_TOKEN: object;
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { FormModel, PathAwareSignal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Узел дерева `model.$` — контейнер (группа/массив), а не лист?
|
|
4
|
+
*
|
|
5
|
+
* Контейнерный узел структурно совместим с `ReadonlySignal` (`peek`/`value`/`subscribe`), поэтому
|
|
6
|
+
* duck-typing «есть `peek` ⇒ это лист» на нём даёт ложное срабатывание. Этот guard — надёжный
|
|
7
|
+
* способ различить: обходчикам дерева нужно спускаться в контейнер, а не читать его целиком.
|
|
8
|
+
*
|
|
9
|
+
* @group Model
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function isModelContainerSignal(value: unknown): boolean;
|
|
2
16
|
/**
|
|
3
17
|
* Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
|
|
4
18
|
*
|
package/dist/state/index.d.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* @module state
|
|
24
24
|
*/
|
|
25
25
|
export { createModel } from './form-model';
|
|
26
|
-
export type { FormModel, ModelArray, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
|
|
26
|
+
export type { FormModel, ModelArray, ModelArraySignals, ModelGroupSignals, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
|
|
27
|
+
export { isModelContainerSignal } from './form-model';
|
|
27
28
|
export { computeFrom, copyFrom, watchField, transformValue, resetWhen, syncFields, revalidateWhen, } from './behaviors-value';
|
|
28
29
|
export type { BehaviorCleanup } from './behaviors-value';
|
|
29
30
|
export { markDerived, isDerived, unmarkDerived } from './derived-registry';
|
package/dist/state/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Signal } from '@preact/signals-core';
|
|
1
|
+
import { ReadonlySignal, Signal } from '@preact/signals-core';
|
|
2
2
|
/**
|
|
3
3
|
* Сигнал, который знает свой путь в модели (`'personalData.lastName'`).
|
|
4
4
|
* Используется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.
|
|
@@ -81,8 +81,8 @@ export interface ModelArray<U> {
|
|
|
81
81
|
[index: number]: ModelValue<U>;
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* Карта под-сигналов объекта в дереве `model.$` (без свойств самого узла — их добавляет
|
|
85
|
+
* {@link ModelGroupSignals}).
|
|
86
86
|
*
|
|
87
87
|
* @group Model
|
|
88
88
|
*/
|
|
@@ -90,16 +90,41 @@ export type ModelSignals<T> = {
|
|
|
90
90
|
[K in keyof T]: ModelSignalNode<T[K]>;
|
|
91
91
|
};
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* `PathAwareSignal<string>`, а не `ModelSignals<never>`.
|
|
93
|
+
* Свойства контейнерного узла дерева `$`, «поверх» карты детей. Ключи детей вырезаются из
|
|
94
|
+
* {@link ReadonlySignal}: доступ к полю формы всегда выигрывает у одноимённого свойства сигнала
|
|
95
|
+
* (так же ведёт себя рантайм), поэтому `{ value: string }` не порождает конфликта типов.
|
|
97
96
|
* @internal
|
|
98
97
|
*/
|
|
99
|
-
type
|
|
98
|
+
type ContainerSignal<T, TKeys extends PropertyKey> = Omit<ReadonlySignal<T>, TKeys>;
|
|
99
|
+
/**
|
|
100
|
+
* Узел-группа дерева `model.$`: доступ к под-сигналам полей ({@link ModelSignals}) И одновременно
|
|
101
|
+
* {@link ReadonlySignal} агрегированного значения группы — `.value`/`.peek()`/`.subscribe()`.
|
|
102
|
+
*
|
|
103
|
+
* ⚠️ Поле формы с именем `value`/`peek`/`subscribe`/`valueOf`/`toString`/`toJSON`/`brand` затеняет
|
|
104
|
+
* одноимённое свойство сигнала (редкий краевой случай); `subscribe` при этом продолжает работать.
|
|
105
|
+
*
|
|
106
|
+
* @group Model
|
|
107
|
+
*/
|
|
108
|
+
export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;
|
|
109
|
+
/**
|
|
110
|
+
* Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
|
|
111
|
+
* и {@link ReadonlySignal} значения массива целиком (реагирует и на правку элемента, и на изменение
|
|
112
|
+
* состава — push/removeAt/move).
|
|
113
|
+
*
|
|
114
|
+
* @group Model
|
|
115
|
+
*/
|
|
116
|
+
export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {
|
|
100
117
|
readonly length: number;
|
|
101
118
|
readonly [index: number]: ModelSignalNode<U>;
|
|
102
|
-
}
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Узел дерева сигналов `model.$`: массив → {@link ModelArraySignals}, Opaque (Date/File/Blob)
|
|
122
|
+
* и примитив → {@link PathAwareSignal}, объект → {@link ModelGroupSignals}. Для примитивных
|
|
123
|
+
* и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
|
|
124
|
+
* `PathAwareSignal<string>`, а не `ModelGroupSignals<never>`.
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U, NonNullable<V>> : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelGroupSignals<NonNullable<V>> : PathAwareSignal<V>;
|
|
103
128
|
/**
|
|
104
129
|
* API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
|
|
105
130
|
*
|
|
@@ -109,8 +134,11 @@ type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? {
|
|
|
109
134
|
* @group Model
|
|
110
135
|
*/
|
|
111
136
|
export interface ModelApi<T> {
|
|
112
|
-
/**
|
|
113
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
|
|
139
|
+
* Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
|
|
140
|
+
*/
|
|
141
|
+
readonly $: ModelGroupSignals<T>;
|
|
114
142
|
/** Снимок значений (без подписки) — для submit. */
|
|
115
143
|
get(): T;
|
|
116
144
|
/**
|
package/dist/state.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { c as s } from "./form-model-
|
|
2
|
-
import { c as
|
|
3
|
-
import {
|
|
4
|
-
import { S as D } from "./subscription-manager-705bls97.js";
|
|
1
|
+
import { c as s, a as r, i as o, m as i, u as t } from "./form-model-DhW2HZOB.js";
|
|
2
|
+
import { c as n, a as d, r as l, b as m, d as f, e as u, f as p, s as b, t as k, w as v } from "./behaviors-value-C7dSEhCc.js";
|
|
3
|
+
import { S as F } from "./subscription-manager-705bls97.js";
|
|
5
4
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
F as SubscriptionManager,
|
|
6
|
+
n as computeFrom,
|
|
7
|
+
d as copyFrom,
|
|
9
8
|
s as createModel,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
m as
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
r as isDerived,
|
|
10
|
+
o as isModelContainerSignal,
|
|
11
|
+
i as markDerived,
|
|
12
|
+
l as resetWhen,
|
|
13
|
+
m as revalidateWhen,
|
|
14
|
+
f as runOutsideEffect,
|
|
15
|
+
u as safeCallback,
|
|
16
|
+
p as safeDebouncedCallback,
|
|
17
|
+
b as syncFields,
|
|
18
|
+
k as transformValue,
|
|
19
|
+
t as unmarkDerived,
|
|
20
|
+
v as watchField
|
|
21
21
|
};
|
package/dist/validation.js
CHANGED
package/dist/validators.d.ts
CHANGED
package/dist/validators.js
CHANGED
|
@@ -1,200 +1,166 @@
|
|
|
1
|
-
import { required as
|
|
2
|
-
import { min as
|
|
3
|
-
import { max as
|
|
4
|
-
import { minLength as
|
|
5
|
-
import { maxLength as
|
|
6
|
-
import { email as
|
|
7
|
-
import { pattern as
|
|
8
|
-
import { url as
|
|
9
|
-
import { phone as
|
|
10
|
-
import { isNumber as
|
|
11
|
-
import { integer as
|
|
12
|
-
import { multipleOf as
|
|
13
|
-
import { nonNegative as
|
|
14
|
-
import { nonZero as
|
|
15
|
-
import { isDate as
|
|
16
|
-
import { minDate as
|
|
17
|
-
import { maxDate as
|
|
18
|
-
import { pastDate as
|
|
19
|
-
import { futureDate as
|
|
20
|
-
import { minAge as
|
|
21
|
-
import { maxAge as
|
|
22
|
-
function
|
|
1
|
+
import { required as z } from "./validators/required.js";
|
|
2
|
+
import { min as y } from "./validators/min.js";
|
|
3
|
+
import { max as b } from "./validators/max.js";
|
|
4
|
+
import { minLength as L } from "./validators/min-length.js";
|
|
5
|
+
import { maxLength as D } from "./validators/max-length.js";
|
|
6
|
+
import { email as T } from "./validators/email.js";
|
|
7
|
+
import { pattern as w } from "./validators/pattern.js";
|
|
8
|
+
import { url as k } from "./validators/url.js";
|
|
9
|
+
import { phone as B } from "./validators/phone.js";
|
|
10
|
+
import { isNumber as Z } from "./validators/is-number.js";
|
|
11
|
+
import { integer as G } from "./validators/integer.js";
|
|
12
|
+
import { multipleOf as I } from "./validators/multiple-of.js";
|
|
13
|
+
import { nonNegative as K } from "./validators/non-negative.js";
|
|
14
|
+
import { nonZero as P } from "./validators/non-zero.js";
|
|
15
|
+
import { isDate as R } from "./validators/is-date.js";
|
|
16
|
+
import { minDate as V } from "./validators/min-date.js";
|
|
17
|
+
import { maxDate as Y } from "./validators/max-date.js";
|
|
18
|
+
import { pastDate as $ } from "./validators/past-date.js";
|
|
19
|
+
import { futureDate as ee } from "./validators/future-date.js";
|
|
20
|
+
import { minAge as ne } from "./validators/min-age.js";
|
|
21
|
+
import { maxAge as ie } from "./validators/max-age.js";
|
|
22
|
+
function s(r) {
|
|
23
23
|
if (typeof r != "object" || r === null) return !1;
|
|
24
|
-
const
|
|
25
|
-
return !(typeof
|
|
24
|
+
const t = r;
|
|
25
|
+
return !(typeof t.name != "string" || t.size !== void 0 && typeof t.size != "number" || t.type !== void 0 && typeof t.type != "string");
|
|
26
26
|
}
|
|
27
27
|
function o(r) {
|
|
28
|
-
return Array.isArray(r) ? r.filter(
|
|
28
|
+
return Array.isArray(r) ? r.filter(s) : s(r) ? [r] : null;
|
|
29
29
|
}
|
|
30
|
-
function
|
|
31
|
-
const e =
|
|
30
|
+
function u(r, t) {
|
|
31
|
+
const e = t.split(",").map((l) => l.trim().toLowerCase()).filter(Boolean);
|
|
32
32
|
if (e.length === 0) return !0;
|
|
33
|
-
const n = r.name.toLowerCase(),
|
|
34
|
-
return e.some((l) => l.startsWith(".") ? n.endsWith(l) : l.endsWith("/*") ?
|
|
33
|
+
const n = r.name.toLowerCase(), i = (r.type ?? "").toLowerCase();
|
|
34
|
+
return e.some((l) => l.startsWith(".") ? n.endsWith(l) : l.endsWith("/*") ? i.startsWith(l.slice(0, -1)) : l.includes("/") ? i === l : !1);
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function m(r, t) {
|
|
37
37
|
return (e) => {
|
|
38
38
|
if (e == null || e === "")
|
|
39
39
|
return null;
|
|
40
40
|
const n = o(e);
|
|
41
41
|
if (n === null || n.length === 0) return null;
|
|
42
|
-
for (const
|
|
43
|
-
if (typeof
|
|
42
|
+
for (const i of n)
|
|
43
|
+
if (typeof i.size == "number" && i.size > r)
|
|
44
44
|
return {
|
|
45
45
|
code: "maxFileSize",
|
|
46
|
-
message:
|
|
46
|
+
message: t?.message ?? "invalid",
|
|
47
47
|
params: {
|
|
48
48
|
maxFileSize: r,
|
|
49
|
-
fileName:
|
|
50
|
-
actualSize:
|
|
51
|
-
...
|
|
49
|
+
fileName: i.name,
|
|
50
|
+
actualSize: i.size,
|
|
51
|
+
...t?.params
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
return null;
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function a(r, t) {
|
|
58
58
|
return (e) => {
|
|
59
59
|
if (e == null || e === "")
|
|
60
60
|
return null;
|
|
61
61
|
const n = o(e);
|
|
62
62
|
if (n === null || n.length === 0) return null;
|
|
63
|
-
for (const
|
|
64
|
-
if (typeof
|
|
63
|
+
for (const i of n)
|
|
64
|
+
if (typeof i.size == "number" && i.size < r)
|
|
65
65
|
return {
|
|
66
66
|
code: "minFileSize",
|
|
67
|
-
message:
|
|
67
|
+
message: t?.message ?? "invalid",
|
|
68
68
|
params: {
|
|
69
69
|
minFileSize: r,
|
|
70
|
-
fileName:
|
|
71
|
-
actualSize:
|
|
72
|
-
...
|
|
70
|
+
fileName: i.name,
|
|
71
|
+
actualSize: i.size,
|
|
72
|
+
...t?.params
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
return null;
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
function
|
|
78
|
+
function p(r, t) {
|
|
79
79
|
return (e) => {
|
|
80
80
|
if (e == null || e === "")
|
|
81
81
|
return null;
|
|
82
82
|
const n = o(e);
|
|
83
83
|
if (n === null || n.length === 0) return null;
|
|
84
|
-
for (const
|
|
85
|
-
if (!
|
|
84
|
+
for (const i of n)
|
|
85
|
+
if (!u(i, r))
|
|
86
86
|
return {
|
|
87
87
|
code: "fileType",
|
|
88
|
-
message:
|
|
89
|
-
params: { accept: r, fileName:
|
|
88
|
+
message: t?.message ?? "invalid",
|
|
89
|
+
params: { accept: r, fileName: i.name, ...t?.params }
|
|
90
90
|
};
|
|
91
91
|
return null;
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
-
function
|
|
94
|
+
function c(r, t) {
|
|
95
95
|
return (e) => {
|
|
96
96
|
if (e == null || e === "")
|
|
97
97
|
return null;
|
|
98
98
|
const n = o(e);
|
|
99
99
|
return n === null || n.length === 0 ? null : n.length > r ? {
|
|
100
100
|
code: "maxFiles",
|
|
101
|
-
message:
|
|
102
|
-
params: { maxFiles: r, actualCount: n.length, ...
|
|
101
|
+
message: t?.message ?? "invalid",
|
|
102
|
+
params: { maxFiles: r, actualCount: n.length, ...t?.params }
|
|
103
103
|
} : null;
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
-
function
|
|
106
|
+
function x(r, t) {
|
|
107
107
|
return (e) => {
|
|
108
108
|
if (e == null || e === "")
|
|
109
109
|
return null;
|
|
110
110
|
const n = o(e);
|
|
111
111
|
return n === null || n.length === 0 ? null : n.length < r ? {
|
|
112
112
|
code: "minFiles",
|
|
113
|
-
message:
|
|
114
|
-
params: { minFiles: r, actualCount: n.length, ...
|
|
113
|
+
message: t?.message ?? "invalid",
|
|
114
|
+
params: { minFiles: r, actualCount: n.length, ...t?.params }
|
|
115
115
|
} : null;
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
function
|
|
118
|
+
function g(r, t) {
|
|
119
119
|
return (e) => {
|
|
120
120
|
if (e == null || e === "")
|
|
121
121
|
return null;
|
|
122
122
|
const n = o(e);
|
|
123
123
|
if (n === null || n.length === 0) return null;
|
|
124
|
-
const
|
|
124
|
+
const i = n.reduce(
|
|
125
125
|
(l, f) => l + (typeof f.size == "number" ? f.size : 0),
|
|
126
126
|
0
|
|
127
127
|
);
|
|
128
|
-
return
|
|
128
|
+
return i > r ? {
|
|
129
129
|
code: "maxTotalFileSize",
|
|
130
|
-
message:
|
|
131
|
-
params: { maxTotalFileSize: r, actualTotal:
|
|
130
|
+
message: t?.message ?? "invalid",
|
|
131
|
+
params: { maxTotalFileSize: r, actualTotal: i, ...t?.params }
|
|
132
132
|
} : null;
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
|
-
const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
136
|
-
__proto__: null,
|
|
137
|
-
email: d,
|
|
138
|
-
fileType: O,
|
|
139
|
-
futureDate: N,
|
|
140
|
-
integer: x,
|
|
141
|
-
isDate: L,
|
|
142
|
-
isFileLike: m,
|
|
143
|
-
isNumber: F,
|
|
144
|
-
matchesFileAccept: s,
|
|
145
|
-
max: p,
|
|
146
|
-
maxAge: _,
|
|
147
|
-
maxDate: C,
|
|
148
|
-
maxFileSize: j,
|
|
149
|
-
maxFiles: k,
|
|
150
|
-
maxLength: g,
|
|
151
|
-
maxTotalFileSize: B,
|
|
152
|
-
min: a,
|
|
153
|
-
minAge: W,
|
|
154
|
-
minDate: T,
|
|
155
|
-
minFileSize: w,
|
|
156
|
-
minFiles: q,
|
|
157
|
-
minLength: c,
|
|
158
|
-
multipleOf: S,
|
|
159
|
-
nonNegative: b,
|
|
160
|
-
nonZero: A,
|
|
161
|
-
pastDate: D,
|
|
162
|
-
pattern: z,
|
|
163
|
-
phone: y,
|
|
164
|
-
required: u,
|
|
165
|
-
toFileArray: o,
|
|
166
|
-
url: h
|
|
167
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
168
135
|
export {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
g as
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
u as required,
|
|
136
|
+
T as email,
|
|
137
|
+
p as fileType,
|
|
138
|
+
ee as futureDate,
|
|
139
|
+
G as integer,
|
|
140
|
+
R as isDate,
|
|
141
|
+
s as isFileLike,
|
|
142
|
+
Z as isNumber,
|
|
143
|
+
u as matchesFileAccept,
|
|
144
|
+
b as max,
|
|
145
|
+
ie as maxAge,
|
|
146
|
+
Y as maxDate,
|
|
147
|
+
m as maxFileSize,
|
|
148
|
+
c as maxFiles,
|
|
149
|
+
D as maxLength,
|
|
150
|
+
g as maxTotalFileSize,
|
|
151
|
+
y as min,
|
|
152
|
+
ne as minAge,
|
|
153
|
+
V as minDate,
|
|
154
|
+
a as minFileSize,
|
|
155
|
+
x as minFiles,
|
|
156
|
+
L as minLength,
|
|
157
|
+
I as multipleOf,
|
|
158
|
+
K as nonNegative,
|
|
159
|
+
P as nonZero,
|
|
160
|
+
$ as pastDate,
|
|
161
|
+
w as pattern,
|
|
162
|
+
B as phone,
|
|
163
|
+
z as required,
|
|
198
164
|
o as toFileArray,
|
|
199
|
-
|
|
165
|
+
k as url
|
|
200
166
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
## 1. API Reference
|
|
2
|
+
|
|
3
|
+
### Imports (CRITICALLY IMPORTANT)
|
|
4
|
+
|
|
5
|
+
Архитектура M1: значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Behaviors работают на сигналах (`model.$.field`), а не на строковых путях.
|
|
6
|
+
|
|
7
|
+
| What | Where |
|
|
8
|
+
| ------------------------------------------------------------------------------------------------------ | --------------------------- |
|
|
9
|
+
| `createModel`, `createForm` | `@reformer/core` |
|
|
10
|
+
| `validateModel`, `defineValidationSchema`, `validate`, `validateAsync`, `validateWhen`, `cross`, `each`, `apply` | `@reformer/core/validation` |
|
|
11
|
+
| `Rule`, `AsyncRule`, `ValidationSchema` (типы) | `@reformer/core/validation` |
|
|
12
|
+
| `useFormControl`, `useFormControlValue`, `useArrayLength` | `@reformer/core` |
|
|
13
|
+
| `FormModel`, `FormProxy`, `FieldNode`, `GroupNode`, `ArrayNode`, `ModelArrayNode` | `@reformer/core` |
|
|
14
|
+
| `ModelSignals`, `ModelArray`, `ModelValue`, `ModelObject`, `PathAwareSignal` | `@reformer/core` |
|
|
15
|
+
| `ValidationError`, `FieldConfig`, `FormSchema`, `FieldControlState` | `@reformer/core` |
|
|
16
|
+
| `computeFrom`, `copyFrom`, `watchField`, `enableWhen`, `disableWhen` | `@reformer/core` (примитивы) |
|
|
17
|
+
| `transformValue`, `resetWhen`, `syncFields`, `revalidateWhen` | `@reformer/core` (примитивы) |
|
|
18
|
+
| `required`, `min`, `max`, `minLength`, `maxLength`, `email`, `pattern`, `url`, `phone` | `@reformer/core/validators` |
|
|
19
|
+
| `isNumber`, `integer`, `multipleOf`, `nonNegative`, `nonZero` | `@reformer/core/validators` |
|
|
20
|
+
| `isDate`, `minDate`, `maxDate`, `pastDate`, `futureDate`, `minAge`, `maxAge` | `@reformer/core/validators` |
|
|
21
|
+
| `defineFormBehavior`, `compute`, `computeFrom`, `copyFrom`, `onChange`, `enableWhen`, `disableWhen` | `@reformer/core/behaviors` |
|
|
22
|
+
| `transformValue`, `resetWhen`, `syncFields`, `revalidateWhen`, `apply`, `applyEach`, `aggregateInto` | `@reformer/core/behaviors` |
|
|
23
|
+
| `exclusiveFlag`, `onDispose`, `getScope`, `effect`, `defer` | `@reformer/core/behaviors` |
|
|
24
|
+
|
|
25
|
+
> **Два способа писать behaviors.** Низкоуровневые примитивы (`computeFrom`, `copyFrom`, `watchField`,
|
|
26
|
+
> `enableWhen`, …) экспортируются из `@reformer/core`, принимают **сигналы** (`model.$.x`), возвращают
|
|
27
|
+
> **cleanup-функцию** и вызываются императивно (например, в `useEffect`). Декларативный DSL
|
|
28
|
+
> (`defineFormBehavior` + операторы) экспортируется из `@reformer/core/behaviors`, регистрирует cleanup
|
|
29
|
+
> сам и передаётся в `createForm({ behavior })`. См. `20-compute-vs-watch.md`.
|
|
30
|
+
|
|
31
|
+
> **Валидация — отдельный слой.** Layout-схема `createForm` НЕ несёт валидаторов. Правила живут в
|
|
32
|
+
> `defineValidationSchema<T>(({ model }) => { validate(model.$.x, [required(), min(50000)]); ... })`
|
|
33
|
+
> из `@reformer/core/validation`; фабрики `required()`, `min(50000)`, `email()` возвращают
|
|
34
|
+
> `Rule<T> = (value) => ValidationError | null` и передаются массивом в `validate(sig, [...])`.
|
|
35
|
+
> Запуск — только внешним раннером `await validateModel(model, schema)` (`Promise<boolean>`, ошибки
|
|
36
|
+
> сам роутит в ноды; `form.validate()`/`form.submit()` schema-валидацию НЕ гоняют).
|
|
37
|
+
|
|
38
|
+
### Type Values
|
|
39
|
+
|
|
40
|
+
- Опциональные числа: `number | null` (конвенция «пользователь очистил поле»)
|
|
41
|
+
- Опциональные строки: `string` (по умолчанию пустая строка) или `string | null`
|
|
42
|
+
- Form-shape тип объявляй как `type`-alias — см. `30-type-safety-recipes.md`
|
|
43
|
+
|
|
44
|
+
### React Hooks Comparison (CRITICALLY IMPORTANT)
|
|
45
|
+
|
|
46
|
+
| Hook | Return Type | Subscribes To | Use Case |
|
|
47
|
+
|------|-------------|---------------|----------|
|
|
48
|
+
| `useFormControl(field)` | `{ value, errors, disabled, touched, valid, invalid, pending, shouldShowError, componentProps }` | Все сигналы поля | Полное состояние поля, инпуты |
|
|
49
|
+
| `useFormControlValue(field)` | `T` (значение напрямую) | Только сигнал value | Условный рендеринг |
|
|
50
|
+
| `useArrayLength(array)` | `number` | Только длина массива | Реактивная длина массива |
|
|
51
|
+
|
|
52
|
+
**CRITICAL**: Не деструктурируй `useFormControlValue`! Он возвращает `T` напрямую, НЕ `{ value: T }`.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// WRONG - will always be undefined!
|
|
56
|
+
const { value: loanType } = useFormControlValue(control.loanType);
|
|
57
|
+
|
|
58
|
+
// CORRECT
|
|
59
|
+
const loanType = useFormControlValue(control.loanType);
|
|
60
|
+
|
|
61
|
+
// CORRECT - useFormControl returns object, destructuring OK
|
|
62
|
+
const { value, errors, disabled } = useFormControl(control.loanType);
|
|
63
|
+
```
|