@reformer/core 11.0.0-beta.7 → 11.0.0-beta.8
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 -3
- package/dist/behaviors.js +211 -174
- package/dist/create-model-BBRHhHwK.js +30 -0
- package/dist/form/behaviors/collections.d.ts +14 -1
- package/dist/form/behaviors/context.d.ts +18 -2
- package/dist/form/behaviors/index.d.ts +3 -1
- package/dist/form/behaviors/internals.d.ts +0 -1
- package/dist/form/behaviors/schema-operators.d.ts +76 -0
- package/dist/form/behaviors/types.d.ts +15 -3
- package/dist/form/create-core-form.d.ts +5 -3
- package/dist/form/create-form.d.ts +28 -53
- package/dist/form/form-bundle.d.ts +136 -0
- package/dist/form/index.d.ts +6 -1
- package/dist/form/nodes/field-node.d.ts +2 -2
- package/dist/form/nodes/group-node.d.ts +1 -1
- package/dist/form/nodes/model-array-node.d.ts +10 -5
- package/dist/form/schema-controller.d.ts +137 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +22 -16
- package/dist/form/types/deep-schema.d.ts +2 -2
- package/dist/form/types/form-proxy.d.ts +1 -1
- package/dist/form/types/schema-node.d.ts +37 -12
- package/dist/form/validation/context.d.ts +6 -1
- package/dist/form/validation/index.d.ts +1 -1
- package/dist/form/validation/operators.d.ts +70 -8
- package/dist/form/validation/types.d.ts +2 -1
- package/dist/index.js +345 -254
- package/dist/model/index.d.ts +5 -0
- package/dist/model/model-nodes.d.ts +67 -4
- package/dist/model/model-signals-proxy.d.ts +8 -1
- package/dist/model/model-value-proxy.d.ts +76 -0
- package/dist/model/types.d.ts +7 -4
- package/dist/model-value-proxy-caTmAiUd.js +419 -0
- package/dist/model.js +19 -17
- package/dist/node-BeV_lV5i.js +100 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/platforms/react/index.d.ts +1 -0
- package/dist/strategy-DgzELWno.js +204 -0
- package/dist/validation.js +7 -6
- package/docs/llms/03-api-signatures.md +4 -4
- package/docs/llms/09-formschema.md +5 -5
- package/docs/llms/10-arrays.md +23 -4
- package/docs/llms/16-ui-components.md +2 -2
- package/docs/llms/18-conditional-fields.md +1 -1
- package/docs/llms/21-array-operations.md +26 -0
- package/docs/llms/28-submit-and-reset.md +3 -3
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/33-validation-strategy.md +2 -2
- package/docs/llms/35-unified-contract.md +246 -0
- package/llms-index.json +1 -1
- package/llms.txt +1148 -122
- package/package.json +1 -1
- package/dist/create-model-B8U-COv-.js +0 -103
- package/dist/model-signals-proxy-2vqS243J.js +0 -250
- package/dist/node-DGEFMTQu.js +0 -18
- package/dist/strategy-BXS_gBfz.js +0 -170
package/dist/model.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import { c as s, e as r, a as
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
1
|
+
import { c as s, e as r, a as l } from "./create-model-BBRHhHwK.js";
|
|
2
|
+
import { a as o, d as c, i as d, b as n, c as t, m as f, u as m } from "./model-value-proxy-caTmAiUd.js";
|
|
3
|
+
import { c as b, a as h, r as p, b as g, d as k, e as v, f as D, s as F, t as S, w as x } from "./behaviors-value-C7dSEhCc.js";
|
|
4
4
|
export {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
o as arrayOf,
|
|
6
|
+
b as computeFrom,
|
|
7
|
+
h as copyFrom,
|
|
7
8
|
s as createModel,
|
|
8
9
|
r as eachLeafSignal,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
l as eachValueSignal,
|
|
11
|
+
c as isDerived,
|
|
12
|
+
d as isModelContainerSignal,
|
|
12
13
|
n as isValueSignal,
|
|
13
14
|
t as markDerived,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
f as modelOf,
|
|
16
|
+
p as resetWhen,
|
|
17
|
+
g as revalidateWhen,
|
|
18
|
+
k as runOutsideEffect,
|
|
19
|
+
v as safeCallback,
|
|
20
|
+
D as safeDebouncedCallback,
|
|
21
|
+
F as syncFields,
|
|
22
|
+
S as transformValue,
|
|
23
|
+
m as unmarkDerived,
|
|
24
|
+
x as watchField
|
|
23
25
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { signal as o, effect as a } from "@preact/signals-core";
|
|
2
|
+
import { b as f, i as c, f as u } from "./model-value-proxy-caTmAiUd.js";
|
|
3
|
+
import { g as d } from "./signal-node-registry-tiwel-TV.js";
|
|
4
|
+
import { d as l } from "./behaviors-value-C7dSEhCc.js";
|
|
5
|
+
function p() {
|
|
6
|
+
const e = /* @__PURE__ */ new Map();
|
|
7
|
+
return {
|
|
8
|
+
hiddenOverrides: /* @__PURE__ */ new Map(),
|
|
9
|
+
propsOverrides: /* @__PURE__ */ new Map(),
|
|
10
|
+
refRegistry: /* @__PURE__ */ new Map(),
|
|
11
|
+
conditionRegistry: /* @__PURE__ */ new Map(),
|
|
12
|
+
effectRegistry: [],
|
|
13
|
+
callbackRegistry: /* @__PURE__ */ new Map(),
|
|
14
|
+
lifecycleRegistry: /* @__PURE__ */ new Map(),
|
|
15
|
+
version: o(0),
|
|
16
|
+
versionFor(t) {
|
|
17
|
+
let r = e.get(t);
|
|
18
|
+
return r || (r = o(0), e.set(t, r)), r;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function g(e = p()) {
|
|
23
|
+
const t = (r) => {
|
|
24
|
+
e.version.value++, e.versionFor(r).value++;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
__overrideMaps: e,
|
|
28
|
+
node: (r) => ({
|
|
29
|
+
setHidden(n) {
|
|
30
|
+
return e.hiddenOverrides.set(r, n), t(r), this;
|
|
31
|
+
},
|
|
32
|
+
resetHidden() {
|
|
33
|
+
return e.hiddenOverrides.delete(r), t(r), this;
|
|
34
|
+
},
|
|
35
|
+
patchProps(n) {
|
|
36
|
+
return e.propsOverrides.set(r, {
|
|
37
|
+
...e.propsOverrides.get(r) ?? {},
|
|
38
|
+
...n
|
|
39
|
+
}), t(r), this;
|
|
40
|
+
},
|
|
41
|
+
resetProps() {
|
|
42
|
+
return e.propsOverrides.delete(r), t(r), this;
|
|
43
|
+
},
|
|
44
|
+
getRef() {
|
|
45
|
+
let n = e.refRegistry.get(r);
|
|
46
|
+
return n || (n = { current: null }, e.refRegistry.set(r, n)), n;
|
|
47
|
+
},
|
|
48
|
+
__selector: r,
|
|
49
|
+
__overrideMaps: e
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function S() {
|
|
54
|
+
const e = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
return {
|
|
56
|
+
scopeOf(t) {
|
|
57
|
+
let r = e.get(t);
|
|
58
|
+
return r || (r = g(), e.set(t, r)), r;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function s(e, t, r) {
|
|
63
|
+
if (e == null || typeof e != "object" || f(e) || c(e) || u(e) || e.$$typeof !== void 0 || r.has(e)) return;
|
|
64
|
+
if (r.add(e), Array.isArray(e)) {
|
|
65
|
+
for (const i of e) s(i, t, r);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const n = e.selector;
|
|
69
|
+
typeof n == "string" && t.add(n);
|
|
70
|
+
for (const i of Object.values(e)) s(i, t, r);
|
|
71
|
+
}
|
|
72
|
+
function b(e, t) {
|
|
73
|
+
const r = /* @__PURE__ */ new Set();
|
|
74
|
+
return s(t, r, /* @__PURE__ */ new WeakSet()), [.../* @__PURE__ */ new Set([
|
|
75
|
+
...e.hiddenOverrides.keys(),
|
|
76
|
+
...e.propsOverrides.keys(),
|
|
77
|
+
...e.conditionRegistry.keys(),
|
|
78
|
+
...e.callbackRegistry.keys(),
|
|
79
|
+
...e.lifecycleRegistry.keys()
|
|
80
|
+
])].filter((i) => !r.has(i));
|
|
81
|
+
}
|
|
82
|
+
function y(e, t, r) {
|
|
83
|
+
return a(() => {
|
|
84
|
+
const n = t(), i = d(e);
|
|
85
|
+
i && l(() => {
|
|
86
|
+
n ? i.enable() : (i.disable(), r?.resetOnDisable && i.reset());
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function M(e, t, r) {
|
|
91
|
+
return y(e, () => !t(), r);
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
g as a,
|
|
95
|
+
p as b,
|
|
96
|
+
S as c,
|
|
97
|
+
M as d,
|
|
98
|
+
y as e,
|
|
99
|
+
b as u
|
|
100
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FormBundle } from '../../form/form-bundle';
|
|
2
|
+
/**
|
|
3
|
+
* Контекст сборки. Значение — бандл `createForm`; вне рендерера — `null`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <FormBundleContext.Provider value={bundle}>
|
|
8
|
+
* <MyWizard />
|
|
9
|
+
* </FormBundleContext.Provider>
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare const FormBundleContext: import('react').Context<FormBundle<any> | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера
|
|
15
|
+
* (например, визард в разметке React-руками получает форму и валидацию пропсами).
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - Форма данных модели.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function StepFooter() {
|
|
22
|
+
* const bundle = useFormBundleContext<CreditForm>();
|
|
23
|
+
* return <button onClick={() => bundle?.validation?.validateAll()}>Проверить</button>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function useFormBundleContext<T = unknown>(): FormBundle<T> | null;
|
|
@@ -20,6 +20,7 @@ export { useFormValidation } from './hooks/use-form-validation';
|
|
|
20
20
|
export type { UseFormValidationArgs, UseFormValidationResult } from './hooks/use-form-validation';
|
|
21
21
|
export { useFormBundle } from './hooks/use-form-bundle';
|
|
22
22
|
export type { FormBundleLike } from './hooks/use-form-bundle';
|
|
23
|
+
export { FormBundleContext, useFormBundleContext } from './form-bundle-context';
|
|
23
24
|
export type { FieldControlState, ArrayControlState } from './hooks/types';
|
|
24
25
|
export { getFieldAdapter, bindFieldProps, FIELD_WRAPPER_ONLY_PROPS, } from './field-binding/field-adapter';
|
|
25
26
|
export type { FieldAdapter, FieldAdapterCarrier, FieldSeam } from './field-binding/field-adapter';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { g as W, m as E, h as $ } from "./model-value-proxy-caTmAiUd.js";
|
|
2
|
+
import { signal as O, effect as P } from "@preact/signals-core";
|
|
3
|
+
import { a as V } from "./create-model-BBRHhHwK.js";
|
|
4
|
+
import { g as h } from "./signal-node-registry-tiwel-TV.js";
|
|
5
|
+
let p = null;
|
|
6
|
+
function A(e, r) {
|
|
7
|
+
const t = p;
|
|
8
|
+
p = e;
|
|
9
|
+
try {
|
|
10
|
+
return r();
|
|
11
|
+
} finally {
|
|
12
|
+
p = t;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function c(e) {
|
|
16
|
+
if (!p)
|
|
17
|
+
throw new Error(
|
|
18
|
+
`[@reformer/core/validation] "${e}" вызван вне схемы валидации — операторы валидации можно вызывать только внутри прогона validateModel(...) (напрямую или через apply/each).`
|
|
19
|
+
);
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
function m(e, r) {
|
|
23
|
+
let t = e.errors.get(r);
|
|
24
|
+
return t || e.errors.set(r, t = []), t;
|
|
25
|
+
}
|
|
26
|
+
const y = (e) => e.whenStack.every((r) => r());
|
|
27
|
+
function _(e, r) {
|
|
28
|
+
const t = c("validate"), n = m(t, e);
|
|
29
|
+
if (!y(t)) return;
|
|
30
|
+
const o = e.peek();
|
|
31
|
+
for (const a of r) {
|
|
32
|
+
const i = a(o, t.model, t.root);
|
|
33
|
+
i && n.push(i);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function j(e, r) {
|
|
37
|
+
const t = c("validateAsync"), n = m(t, e);
|
|
38
|
+
if (!y(t)) return;
|
|
39
|
+
const o = e.peek(), a = t.signal;
|
|
40
|
+
for (const i of r)
|
|
41
|
+
t.pending.push(
|
|
42
|
+
i(o, { signal: a }).then(
|
|
43
|
+
(s) => {
|
|
44
|
+
s && !a.aborted && n.push(s);
|
|
45
|
+
},
|
|
46
|
+
() => {
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function z(e, r) {
|
|
52
|
+
const t = c("validateWhen");
|
|
53
|
+
t.whenStack.push(e);
|
|
54
|
+
try {
|
|
55
|
+
r();
|
|
56
|
+
} finally {
|
|
57
|
+
t.whenStack.pop();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function G(e, r) {
|
|
61
|
+
const t = c("cross"), n = m(t, e);
|
|
62
|
+
if (!y(t)) return;
|
|
63
|
+
const o = r(t.model.get());
|
|
64
|
+
o && n.push(o);
|
|
65
|
+
}
|
|
66
|
+
function F(e, r, t) {
|
|
67
|
+
const n = r;
|
|
68
|
+
A(
|
|
69
|
+
{ ...e, model: n },
|
|
70
|
+
() => t({ model: n })
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function M(e, r) {
|
|
74
|
+
const t = $(r);
|
|
75
|
+
if (!t)
|
|
76
|
+
throw new TypeError(
|
|
77
|
+
`[@reformer/core/validation] ${e}: ожидался массив под-форм модели — \`model.$.<массив>\`. Массив примитивов — одно значение поля, его проверяют целиком: \`validate(model.$.<массив>, [...])\`.`
|
|
78
|
+
);
|
|
79
|
+
return E(t);
|
|
80
|
+
}
|
|
81
|
+
function I(e, r) {
|
|
82
|
+
const t = c("applyEach"), n = M("applyEach", e), o = n.length;
|
|
83
|
+
for (let a = 0; a < o; a++) F(t, n.at(a), r);
|
|
84
|
+
}
|
|
85
|
+
function J(e, r) {
|
|
86
|
+
c("each");
|
|
87
|
+
const t = M("each", e), n = t.length;
|
|
88
|
+
for (let o = 0; o < n; o++) r(t.at(o));
|
|
89
|
+
}
|
|
90
|
+
function K(...e) {
|
|
91
|
+
const r = c("apply");
|
|
92
|
+
if (typeof e[0] == "function") {
|
|
93
|
+
for (const o of e) o({ model: r.model });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const [t, n] = e;
|
|
97
|
+
for (const o of Array.isArray(t) ? t : [t]) {
|
|
98
|
+
const a = W(o);
|
|
99
|
+
if (!a)
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
"[@reformer/core/validation] apply: ожидалась группа модели — `model.$.<группа>`. Массив под-форм подключают через `applyEach`."
|
|
102
|
+
);
|
|
103
|
+
F(r, E(a), n);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function Q(e) {
|
|
107
|
+
return e;
|
|
108
|
+
}
|
|
109
|
+
const x = /* @__PURE__ */ new WeakMap();
|
|
110
|
+
function C(e, r) {
|
|
111
|
+
let t = x.get(e);
|
|
112
|
+
t || x.set(e, t = /* @__PURE__ */ new WeakMap());
|
|
113
|
+
let n = t.get(r);
|
|
114
|
+
return n || t.set(r, n = { fields: /* @__PURE__ */ new Set(), ac: null }), n;
|
|
115
|
+
}
|
|
116
|
+
const L = (e) => {
|
|
117
|
+
for (const r of e.values()) for (const t of r) if (t.severity !== "warning") return !0;
|
|
118
|
+
return !1;
|
|
119
|
+
};
|
|
120
|
+
function H(e) {
|
|
121
|
+
return e.aborted ? Promise.resolve() : new Promise((r) => {
|
|
122
|
+
e.addEventListener("abort", () => r(), { once: !0 });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function q(e, r, t) {
|
|
126
|
+
const n = C(e, r);
|
|
127
|
+
n.ac?.abort();
|
|
128
|
+
const o = new AbortController();
|
|
129
|
+
n.ac = o;
|
|
130
|
+
const a = {
|
|
131
|
+
model: e,
|
|
132
|
+
root: e,
|
|
133
|
+
errors: /* @__PURE__ */ new Map(),
|
|
134
|
+
pending: [],
|
|
135
|
+
whenStack: [],
|
|
136
|
+
signal: o.signal
|
|
137
|
+
};
|
|
138
|
+
if (A(a, () => r({ model: e })), a.pending.length && await Promise.race([Promise.all(a.pending), H(o.signal)]), o.signal.aborted) return !1;
|
|
139
|
+
const i = new Set(a.errors.keys());
|
|
140
|
+
for (const s of n.fields) i.has(s) || h(s)?.setErrors([]);
|
|
141
|
+
for (const s of i) h(s)?.setErrors(a.errors.get(s) ?? []);
|
|
142
|
+
if (t?.touch) for (const s of i) h(s)?.markAsTouched();
|
|
143
|
+
return n.fields = i, !L(a.errors);
|
|
144
|
+
}
|
|
145
|
+
function U(e, r, t = {}) {
|
|
146
|
+
const { strategy: n = "submit", debounce: o = 0, liveAfterSubmit: a = "change" } = t, i = O(!1);
|
|
147
|
+
let s = 0, w = !1, u, l;
|
|
148
|
+
const b = async (f) => {
|
|
149
|
+
s++, i.value = !0;
|
|
150
|
+
try {
|
|
151
|
+
return await q(e, r, { touch: f });
|
|
152
|
+
} finally {
|
|
153
|
+
--s === 0 && (i.value = !1);
|
|
154
|
+
}
|
|
155
|
+
}, g = () => {
|
|
156
|
+
b(!1);
|
|
157
|
+
}, T = () => {
|
|
158
|
+
if (!o) {
|
|
159
|
+
g();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
l && clearTimeout(l), l = setTimeout(g, o);
|
|
163
|
+
}, k = (f, d) => {
|
|
164
|
+
let S = !0;
|
|
165
|
+
return P(() => {
|
|
166
|
+
if (V(e, f), S) {
|
|
167
|
+
S = !1;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
n === "afterFirstSubmit" && !w || d();
|
|
171
|
+
});
|
|
172
|
+
}, v = {
|
|
173
|
+
async validate() {
|
|
174
|
+
return w = !0, b(!0);
|
|
175
|
+
},
|
|
176
|
+
start() {
|
|
177
|
+
if (u) return () => v.dispose();
|
|
178
|
+
const f = n === "afterFirstSubmit" ? a : n;
|
|
179
|
+
return f === "change" ? u = k((d) => void d.value, T) : f === "blur" && (u = k((d) => void h(d)?.touched.value, g)), () => v.dispose();
|
|
180
|
+
},
|
|
181
|
+
dispose() {
|
|
182
|
+
l && (clearTimeout(l), l = void 0), u?.(), u = void 0;
|
|
183
|
+
},
|
|
184
|
+
get isValidating() {
|
|
185
|
+
return i.value;
|
|
186
|
+
},
|
|
187
|
+
get validating() {
|
|
188
|
+
return i;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return v;
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
j as a,
|
|
195
|
+
z as b,
|
|
196
|
+
G as c,
|
|
197
|
+
K as d,
|
|
198
|
+
J as e,
|
|
199
|
+
I as f,
|
|
200
|
+
Q as g,
|
|
201
|
+
q as h,
|
|
202
|
+
U as i,
|
|
203
|
+
_ as v
|
|
204
|
+
};
|
package/dist/validation.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { d as s,
|
|
1
|
+
import { d as s, f as i, i as d, c as l, g as t, e as c, v as o, a as h, h as n, b as p } from "./strategy-DgzELWno.js";
|
|
2
2
|
export {
|
|
3
3
|
s as apply,
|
|
4
|
+
i as applyEach,
|
|
4
5
|
d as createFormValidation,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
l as cross,
|
|
7
|
+
t as defineValidationSchema,
|
|
8
|
+
c as each,
|
|
8
9
|
o as validate,
|
|
9
|
-
|
|
10
|
+
h as validateAsync,
|
|
10
11
|
n as validateModel,
|
|
11
|
-
|
|
12
|
+
p as validateWhen
|
|
12
13
|
};
|
|
@@ -11,7 +11,7 @@ createModel<T extends object>(initial: T): FormModel<T>
|
|
|
11
11
|
// model.arrayField → ModelArray<Item> (push/removeAt/insertAt/move/swap/clear/at/map/length)
|
|
12
12
|
|
|
13
13
|
// Форма (ноды поверх сигналов модели)
|
|
14
|
-
|
|
14
|
+
createFormFromModel<T>({ model, schema, behavior? }): FormProxy<T>
|
|
15
15
|
// form.<field> → FieldNode / GroupNode / FormArrayProxy
|
|
16
16
|
// form.<field>.setValue(v) / .value.value / .errors.value / .disabled.value
|
|
17
17
|
// form.<field>.enable() / .disable() / .reset() / .markAsTouched() / .setErrors([...])
|
|
@@ -67,7 +67,7 @@ maxAge(years: number, options?: { message?: string })
|
|
|
67
67
|
`ValidationSchema` и гоняются раннером `validateModel` по требованию.
|
|
68
68
|
|
|
69
69
|
```typescript
|
|
70
|
-
import { createModel, createForm } from '@reformer/core';
|
|
70
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
71
71
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
72
72
|
import { required, min, max, email } from '@reformer/core/validators';
|
|
73
73
|
|
|
@@ -75,7 +75,7 @@ type Loan = { email: string; age: number | null; amount: number | null };
|
|
|
75
75
|
const model = createModel<Loan>({ email: '', age: null, amount: null });
|
|
76
76
|
|
|
77
77
|
// layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
|
|
78
|
-
const form =
|
|
78
|
+
const form = createFormFromModel({
|
|
79
79
|
model,
|
|
80
80
|
schema: {
|
|
81
81
|
email: { value: model.$.email, component: Input },
|
|
@@ -262,7 +262,7 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
const form =
|
|
265
|
+
const form = createFormFromModel({ model, schema, behavior });
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
DSL-операторы: `compute` (auto-tracking, без явного списка источников), `computeFrom`, `copyFrom`,
|
|
@@ -25,7 +25,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
25
25
|
### Primitive Fields
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import { createModel,
|
|
28
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
29
29
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
30
30
|
import { required } from '@reformer/core/validators';
|
|
31
31
|
import { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
@@ -66,7 +66,7 @@ const schema = {
|
|
|
66
66
|
},
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
const form =
|
|
69
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### Nested Objects
|
|
@@ -111,7 +111,7 @@ const schema = {
|
|
|
111
111
|
|
|
112
112
|
```typescript
|
|
113
113
|
// M1: данные из модели + схема (+ опциональный декларативный behavior)
|
|
114
|
-
const form =
|
|
114
|
+
const form = createFormFromModel<MyForm>({
|
|
115
115
|
model, // FormModel<MyForm> — обязателен
|
|
116
116
|
schema, // дерево узлов, привязанных к сигналам
|
|
117
117
|
behavior: myBehavior, // опционально: defineFormBehavior(...) из @reformer/core/behaviors
|
|
@@ -126,12 +126,12 @@ model.items.push({ id: '1', name: 'Item' }); // операции над масс
|
|
|
126
126
|
> **Тип `schema` в M1 — `unknown`, не `FormSchema<T>`.** Это осознанно: интерпретатор обходит
|
|
127
127
|
> произвольную структуру и собирает листья `{ value: signal, component?, ... }`, поэтому обёртки
|
|
128
128
|
> (`{ children: [...] }`, wizard/steps) компилируются. Строгий `FormSchema<T>` (keyed-map по полям
|
|
129
|
-
> `T`) применяется только на legacy-пути `
|
|
129
|
+
> `T`) применяется только на legacy-пути `createLegacyForm(schema)` без модели.
|
|
130
130
|
|
|
131
131
|
### createForm Returns a Proxy
|
|
132
132
|
|
|
133
133
|
```typescript
|
|
134
|
-
const form =
|
|
134
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
135
135
|
|
|
136
136
|
form.email; // FieldNode<string> — TypeScript знает тип
|
|
137
137
|
form.address.city; // FieldNode<string> — вложенный доступ
|
package/docs/llms/10-arrays.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
элемента из его под-модели (`FormModel<Item>`).
|
|
7
7
|
|
|
8
8
|
```typescript
|
|
9
|
-
import { createModel,
|
|
9
|
+
import { createModel, createFormFromModel, type FormModel } from '@reformer/core';
|
|
10
10
|
import { Input, InputNumber } from '@reformer/ui-kit';
|
|
11
11
|
|
|
12
12
|
type Item = { id: string; name: string; price: number };
|
|
@@ -25,7 +25,7 @@ const schema = {
|
|
|
25
25
|
items: { array: model.items, item: itemSchema },
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const form =
|
|
28
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
> **Type constraint:** тип элемента `Item` объявляй через `type`-alias (не `interface`) — иначе
|
|
@@ -47,7 +47,7 @@ const model = createModel<MyForm>({ tags: [] });
|
|
|
47
47
|
const schema = {
|
|
48
48
|
tags: { value: model.$.tags, component: SelectMulti }, // поле над массивом целиком
|
|
49
49
|
};
|
|
50
|
-
const form =
|
|
50
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
51
51
|
|
|
52
52
|
form.tags.setValue(['a', 'b']); // FieldNode<string[]>
|
|
53
53
|
model.$.tags.value = ['c']; // ручка значения: заменяет массив целиком
|
|
@@ -62,6 +62,25 @@ validate(model.$.tags, [required(), maxLength(3)]); // правило получ
|
|
|
62
62
|
- Запись в узел-ГРУППУ (`model.$.<группа>.value = …`) — ошибка: группа пишется по полям или через
|
|
63
63
|
`model.patch(...)`.
|
|
64
64
|
|
|
65
|
+
### Массив в группе и в строке другого массива
|
|
66
|
+
|
|
67
|
+
Узел `{ array, item }` стоит на любой глубине: массив в группе получает ноду `form.<группа>.<массив>`,
|
|
68
|
+
массив в строке — `form.<массив>.at(i).<массив>`. Под-схема строки объявляет свои массивы так же.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const contactItem = (contact: FormModel<Contact>) => ({
|
|
72
|
+
name: { value: contact.$.name, component: Input },
|
|
73
|
+
phones: { array: contact.phones, item: phoneItem }, // массив в строке массива
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const schema = {
|
|
77
|
+
hotlines: { array: model.details.hotlines, item: phoneItem }, // массив в группе
|
|
78
|
+
contacts: { array: model.contacts, item: contactItem },
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Поведение строк вложенного массива — `applyEach` внутри схемы строки, см. `21-array-operations.md`.
|
|
83
|
+
|
|
65
84
|
### Один массив — три слоя (три разных движка)
|
|
66
85
|
|
|
67
86
|
Одна и та же коллекция описывается **тремя разными формами** — по одной на движок. Их легко
|
|
@@ -73,7 +92,7 @@ validate(model.$.tags, [required(), maxLength(3)]); // правило получ
|
|
|
73
92
|
`model.$.properties`.
|
|
74
93
|
|
|
75
94
|
```typescript
|
|
76
|
-
// узел схемы для
|
|
95
|
+
// узел схемы для createFormFromModel({ model, schema })
|
|
77
96
|
properties: { array: model.properties, item: propertyItem },
|
|
78
97
|
```
|
|
79
98
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
```tsx
|
|
21
21
|
import { useMemo } from 'react';
|
|
22
|
-
import { createModel,
|
|
22
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
23
23
|
import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
|
|
24
24
|
|
|
25
25
|
type RegistrationForm = {
|
|
@@ -54,7 +54,7 @@ function RegistrationPage() {
|
|
|
54
54
|
componentProps: { label: 'I agree to terms' },
|
|
55
55
|
},
|
|
56
56
|
};
|
|
57
|
-
return
|
|
57
|
+
return createFormFromModel<RegistrationForm>({ model, schema });
|
|
58
58
|
}, []);
|
|
59
59
|
|
|
60
60
|
return (
|
|
@@ -56,7 +56,7 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
|
|
|
56
56
|
- **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
|
|
57
57
|
только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
|
|
58
58
|
строковые пути; сигнала для несуществующего поля нет.
|
|
59
|
-
- Поведение подключается к форме через `
|
|
59
|
+
- Поведение подключается к форме через `createFormFromModel({ model, schema, behavior })` — форма владеет
|
|
60
60
|
жизненным циклом, DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
|
|
61
61
|
- Таргетом может быть и **группа**: `enableWhen(model.$.residenceAddress, () => model.sameAsRegistration === false)`
|
|
62
62
|
проходит по поддереву; без `resetOnDisable` значение группы сохраняется (удобно, когда оно копируется
|
|
@@ -76,6 +76,32 @@ const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Вложенные области — массив в группе и в строке массива
|
|
80
|
+
|
|
81
|
+
Узел `{ array, item }` и операторы `applyEach` / `apply` / `enableWhen` работают на любой глубине:
|
|
82
|
+
цель оператора — **ручка** `model.$.…`, под-модель и нода находятся по её идентичности, а не по
|
|
83
|
+
пути.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const phoneBehavior = defineFormBehavior<Phone>(({ model: phone }) => {
|
|
87
|
+
enableWhen(phone.$.number, () => phone.primary);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const contactBehavior = defineFormBehavior<Contact>(({ model: contact }) => {
|
|
91
|
+
applyEach(contact.$.phones, phoneBehavior); // массив внутри строки
|
|
92
|
+
enableWhen(contact.$.address, () => contact.quantity > 0); // группа внутри строки
|
|
93
|
+
apply(contact.$.address, addressBehavior); // под-схема получает настоящую под-модель
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
97
|
+
applyEach(model.$.contacts, contactBehavior);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Ручка → value-фасад: `modelOf(model.$.address)` — под-модель (`get` / `set` / `patch`),
|
|
102
|
+
`modelOf(model.$.phones)` — фасад массива (`push` / `removeAt` / `at`). Нода формы по ручке —
|
|
103
|
+
`getNodeForSignal(model.$.address)`.
|
|
104
|
+
|
|
79
105
|
### Aggregate write — aggregateInto
|
|
80
106
|
|
|
81
107
|
Агрегатная запись в строки (например, «последняя строка = 100 − Σ остальных»):
|
|
@@ -48,7 +48,7 @@ form.<field>.clearErrors();
|
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
50
|
import { useMemo } from 'react';
|
|
51
|
-
import { createModel, createForm } from '@reformer/core';
|
|
51
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
52
52
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
53
53
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
54
54
|
|
|
@@ -66,7 +66,7 @@ function RegistrationForm() {
|
|
|
66
66
|
const { model, form } = useMemo(() => {
|
|
67
67
|
const m = createModel<RegistrationFormData>({ username: '', email: '', password: '' });
|
|
68
68
|
// schema здесь — layout-дерево нод (компоненты/раскладка), без валидаторов.
|
|
69
|
-
return { model: m, form:
|
|
69
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildLayout(m) }) };
|
|
70
70
|
}, []);
|
|
71
71
|
|
|
72
72
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -198,7 +198,7 @@ const handleSubmit = async (e) => {
|
|
|
198
198
|
|
|
199
199
|
```typescript
|
|
200
200
|
// ❌ Валидаторы в layout-дереве, отдаваемом в createForm — layout не несёт правил
|
|
201
|
-
|
|
201
|
+
createFormFromModel({ model, schema: { username: { value: '', validators: [required()] } } });
|
|
202
202
|
|
|
203
203
|
// ✅ Валидация — отдельная схема, прогоняется раннером
|
|
204
204
|
const schema = defineValidationSchema<T>(({ model }) => {
|
|
@@ -30,7 +30,7 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
30
30
|
### Initial values в модели
|
|
31
31
|
|
|
32
32
|
```typescript
|
|
33
|
-
import { createModel,
|
|
33
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
34
34
|
import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
35
35
|
|
|
36
36
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
@@ -51,7 +51,7 @@ const schema = {
|
|
|
51
51
|
},
|
|
52
52
|
marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
|
|
53
53
|
};
|
|
54
|
-
const form =
|
|
54
|
+
const form = createFormFromModel({ model, schema });
|
|
55
55
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
56
56
|
// после правок: model.reset() возвращает к этим значениям
|
|
57
57
|
```
|
|
@@ -138,14 +138,14 @@ export const addressBehavior = defineFormBehavior<AddressForm>(({ model, form })
|
|
|
138
138
|
// ❌ model/form пересоздаются на каждый рендер → preload запускается каждый раз
|
|
139
139
|
function MyForm() {
|
|
140
140
|
const model = createModel<T>(initial); // КАЖДЫЙ рендер!
|
|
141
|
-
const form =
|
|
141
|
+
const form = createFormFromModel({ model, schema });
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// ✅ Стабильные ссылки через useMemo
|
|
145
145
|
function MyForm() {
|
|
146
146
|
const { model, form } = useMemo(() => {
|
|
147
147
|
const m = createModel<T>(initial);
|
|
148
|
-
return { model: m, form:
|
|
148
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildSchema(m) }) };
|
|
149
149
|
}, []);
|
|
150
150
|
}
|
|
151
151
|
```
|