@reformer/core 11.0.0-beta.2 → 11.0.0-beta.3
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/form/create-core-form.d.ts +70 -0
- package/dist/form/hooks/use-form-bundle.d.ts +26 -0
- package/dist/form/index.d.ts +6 -0
- package/dist/form/validation-config.d.ts +73 -0
- package/dist/form/validation-strategy.d.ts +8 -2
- package/dist/index.js +528 -474
- package/dist/validation.js +92 -92
- package/docs/llms/02-quick-start.md +14 -7
- package/docs/llms/15-project-structure.md +12 -8
- package/llms.txt +270 -15
- package/package.json +1 -1
package/dist/validation.js
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
|
-
import { signal as
|
|
2
|
-
import { e as
|
|
1
|
+
import { signal as A, effect as M } from "@preact/signals-core";
|
|
2
|
+
import { e as E } from "./form-model-DhW2HZOB.js";
|
|
3
3
|
import { g as p } from "./signal-node-registry-tiwel-TV.js";
|
|
4
|
+
function B(e, n, t = {}) {
|
|
5
|
+
const { strategy: r = "submit", debounce: o = 0, liveAfterSubmit: i = "change" } = t, s = A(!1);
|
|
6
|
+
let a = 0, c = !1, d, l;
|
|
7
|
+
const w = async (u) => {
|
|
8
|
+
a++, s.value = !0;
|
|
9
|
+
try {
|
|
10
|
+
return await W(e, n, { touch: u });
|
|
11
|
+
} finally {
|
|
12
|
+
--a === 0 && (s.value = !1);
|
|
13
|
+
}
|
|
14
|
+
}, h = () => {
|
|
15
|
+
w(!1);
|
|
16
|
+
}, F = () => {
|
|
17
|
+
if (!o) {
|
|
18
|
+
h();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
l && clearTimeout(l), l = setTimeout(h, o);
|
|
22
|
+
}, k = (u, g) => {
|
|
23
|
+
let S = !0;
|
|
24
|
+
return M(() => {
|
|
25
|
+
if (E(e, u), S) {
|
|
26
|
+
S = !1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
r === "afterFirstSubmit" && !c || g();
|
|
30
|
+
});
|
|
31
|
+
}, m = {
|
|
32
|
+
async validate() {
|
|
33
|
+
return c = !0, w(!0);
|
|
34
|
+
},
|
|
35
|
+
start() {
|
|
36
|
+
if (d) return () => m.dispose();
|
|
37
|
+
const u = r === "afterFirstSubmit" ? i : r;
|
|
38
|
+
return u === "change" ? d = k((g) => void g.value, F) : u === "blur" && (d = k((g) => void p(g)?.touched.value, h)), () => m.dispose();
|
|
39
|
+
},
|
|
40
|
+
dispose() {
|
|
41
|
+
l && (clearTimeout(l), l = void 0), d?.(), d = void 0;
|
|
42
|
+
},
|
|
43
|
+
get isValidating() {
|
|
44
|
+
return s.value;
|
|
45
|
+
},
|
|
46
|
+
get validating() {
|
|
47
|
+
return s;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return m;
|
|
51
|
+
}
|
|
4
52
|
let v = null;
|
|
5
53
|
function f(e) {
|
|
6
54
|
if (!v)
|
|
@@ -9,83 +57,83 @@ function f(e) {
|
|
|
9
57
|
);
|
|
10
58
|
return v;
|
|
11
59
|
}
|
|
12
|
-
function b(e,
|
|
13
|
-
let t = e.errors.get(
|
|
14
|
-
return t || e.errors.set(
|
|
60
|
+
function b(e, n) {
|
|
61
|
+
let t = e.errors.get(n);
|
|
62
|
+
return t || e.errors.set(n, t = []), t;
|
|
15
63
|
}
|
|
16
|
-
const y = (e) => e.whenStack.every((
|
|
17
|
-
function D(e,
|
|
18
|
-
const t = f("validate"),
|
|
64
|
+
const y = (e) => e.whenStack.every((n) => n());
|
|
65
|
+
function D(e, n) {
|
|
66
|
+
const t = f("validate"), r = b(t, e);
|
|
19
67
|
if (!y(t)) return;
|
|
20
68
|
const o = e.peek();
|
|
21
|
-
for (const i of
|
|
69
|
+
for (const i of n) {
|
|
22
70
|
const s = i(o, t.model, t.model);
|
|
23
|
-
s &&
|
|
71
|
+
s && r.push(s);
|
|
24
72
|
}
|
|
25
73
|
}
|
|
26
|
-
function N(e,
|
|
27
|
-
const t = f("validateAsync"),
|
|
74
|
+
function N(e, n) {
|
|
75
|
+
const t = f("validateAsync"), r = b(t, e);
|
|
28
76
|
if (!y(t)) return;
|
|
29
77
|
const o = e.peek(), i = t.signal;
|
|
30
|
-
for (const s of
|
|
78
|
+
for (const s of n)
|
|
31
79
|
t.pending.push(
|
|
32
80
|
s(o, { signal: i }).then(
|
|
33
81
|
(a) => {
|
|
34
|
-
a && !i.aborted &&
|
|
82
|
+
a && !i.aborted && r.push(a);
|
|
35
83
|
},
|
|
36
84
|
() => {
|
|
37
85
|
}
|
|
38
86
|
)
|
|
39
87
|
);
|
|
40
88
|
}
|
|
41
|
-
function R(e,
|
|
89
|
+
function R(e, n) {
|
|
42
90
|
const t = f("validateWhen");
|
|
43
91
|
t.whenStack.push(e);
|
|
44
92
|
try {
|
|
45
|
-
|
|
93
|
+
n();
|
|
46
94
|
} finally {
|
|
47
95
|
t.whenStack.pop();
|
|
48
96
|
}
|
|
49
97
|
}
|
|
50
|
-
function _(e,
|
|
51
|
-
const t = f("cross"),
|
|
98
|
+
function _(e, n) {
|
|
99
|
+
const t = f("cross"), r = b(t, e);
|
|
52
100
|
if (!y(t)) return;
|
|
53
|
-
const o =
|
|
54
|
-
o &&
|
|
101
|
+
const o = n(t.model.get());
|
|
102
|
+
o && r.push(o);
|
|
55
103
|
}
|
|
56
|
-
function $(e,
|
|
104
|
+
function $(e, n) {
|
|
57
105
|
f("each");
|
|
58
106
|
const t = e.length;
|
|
59
|
-
for (let
|
|
107
|
+
for (let r = 0; r < t; r++) n(e.at(r));
|
|
60
108
|
}
|
|
61
109
|
function j(...e) {
|
|
62
110
|
const t = f("apply").model;
|
|
63
|
-
for (const
|
|
111
|
+
for (const r of e) r({ model: t });
|
|
64
112
|
}
|
|
65
113
|
function z(e) {
|
|
66
114
|
return e;
|
|
67
115
|
}
|
|
68
|
-
const
|
|
69
|
-
function
|
|
70
|
-
let t =
|
|
71
|
-
t ||
|
|
72
|
-
let
|
|
73
|
-
return
|
|
116
|
+
const x = /* @__PURE__ */ new WeakMap();
|
|
117
|
+
function L(e, n) {
|
|
118
|
+
let t = x.get(e);
|
|
119
|
+
t || x.set(e, t = /* @__PURE__ */ new WeakMap());
|
|
120
|
+
let r = t.get(n);
|
|
121
|
+
return r || t.set(n, r = { fields: /* @__PURE__ */ new Set(), ac: null }), r;
|
|
74
122
|
}
|
|
75
|
-
const
|
|
76
|
-
for (const
|
|
123
|
+
const P = (e) => {
|
|
124
|
+
for (const n of e.values()) for (const t of n) if (t.severity !== "warning") return !0;
|
|
77
125
|
return !1;
|
|
78
126
|
};
|
|
79
|
-
function
|
|
80
|
-
return e.aborted ? Promise.resolve() : new Promise((
|
|
81
|
-
e.addEventListener("abort", () =>
|
|
127
|
+
function T(e) {
|
|
128
|
+
return e.aborted ? Promise.resolve() : new Promise((n) => {
|
|
129
|
+
e.addEventListener("abort", () => n(), { once: !0 });
|
|
82
130
|
});
|
|
83
131
|
}
|
|
84
|
-
async function
|
|
85
|
-
const
|
|
86
|
-
|
|
132
|
+
async function W(e, n, t) {
|
|
133
|
+
const r = L(e, n);
|
|
134
|
+
r.ac?.abort();
|
|
87
135
|
const o = new AbortController();
|
|
88
|
-
|
|
136
|
+
r.ac = o;
|
|
89
137
|
const i = {
|
|
90
138
|
model: e,
|
|
91
139
|
errors: /* @__PURE__ */ new Map(),
|
|
@@ -95,73 +143,25 @@ async function V(e, r, t) {
|
|
|
95
143
|
}, s = v;
|
|
96
144
|
v = i;
|
|
97
145
|
try {
|
|
98
|
-
|
|
146
|
+
n({ model: e });
|
|
99
147
|
} finally {
|
|
100
148
|
v = s;
|
|
101
149
|
}
|
|
102
|
-
if (i.pending.length && await Promise.race([Promise.all(i.pending),
|
|
150
|
+
if (i.pending.length && await Promise.race([Promise.all(i.pending), T(o.signal)]), o.signal.aborted) return !1;
|
|
103
151
|
const a = new Set(i.errors.keys());
|
|
104
|
-
for (const c of
|
|
152
|
+
for (const c of r.fields) a.has(c) || p(c)?.setErrors([]);
|
|
105
153
|
for (const c of a) p(c)?.setErrors(i.errors.get(c) ?? []);
|
|
106
154
|
if (t?.touch) for (const c of a) p(c)?.markAsTouched();
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
function G(e, r, t = {}) {
|
|
110
|
-
const { strategy: n = "submit", debounce: o = 0, liveAfterSubmit: i = "change" } = t, s = M(!1);
|
|
111
|
-
let a = 0, c = !1, w = !1, d, l;
|
|
112
|
-
const k = async (u) => {
|
|
113
|
-
a++, s.value = !0;
|
|
114
|
-
try {
|
|
115
|
-
return await V(e, r, { touch: u });
|
|
116
|
-
} finally {
|
|
117
|
-
--a === 0 && (s.value = !1);
|
|
118
|
-
}
|
|
119
|
-
}, h = () => {
|
|
120
|
-
k(!1);
|
|
121
|
-
}, A = () => {
|
|
122
|
-
if (!o) {
|
|
123
|
-
h();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
l && clearTimeout(l), l = setTimeout(h, o);
|
|
127
|
-
}, S = (u, g) => {
|
|
128
|
-
let x = !0;
|
|
129
|
-
return E(() => {
|
|
130
|
-
if (L(e, u), x) {
|
|
131
|
-
x = !1;
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
n === "afterFirstSubmit" && !c || g();
|
|
135
|
-
});
|
|
136
|
-
}, m = {
|
|
137
|
-
async validate() {
|
|
138
|
-
return c = !0, k(!0);
|
|
139
|
-
},
|
|
140
|
-
start() {
|
|
141
|
-
if (w || d) return () => m.dispose();
|
|
142
|
-
const u = n === "afterFirstSubmit" ? i : n;
|
|
143
|
-
return u === "change" ? d = S((g) => void g.value, A) : u === "blur" && (d = S((g) => void p(g)?.touched.value, h)), () => m.dispose();
|
|
144
|
-
},
|
|
145
|
-
dispose() {
|
|
146
|
-
w = !0, l && (clearTimeout(l), l = void 0), d?.(), d = void 0;
|
|
147
|
-
},
|
|
148
|
-
get isValidating() {
|
|
149
|
-
return s.value;
|
|
150
|
-
},
|
|
151
|
-
get validating() {
|
|
152
|
-
return s;
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
return m;
|
|
155
|
+
return r.fields = a, !P(i.errors);
|
|
156
156
|
}
|
|
157
157
|
export {
|
|
158
158
|
j as apply,
|
|
159
|
-
|
|
159
|
+
B as createFormValidation,
|
|
160
160
|
_ as cross,
|
|
161
161
|
z as defineValidationSchema,
|
|
162
162
|
$ as each,
|
|
163
163
|
D as validate,
|
|
164
164
|
N as validateAsync,
|
|
165
|
-
|
|
165
|
+
W as validateModel,
|
|
166
166
|
R as validateWhen
|
|
167
167
|
};
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
> `Checkbox`-обёртки с `label`-prop'ами — это anti-pattern. См.
|
|
8
8
|
> `find_recipe(package="@reformer/ui-kit", topic="form-field-integration")`.
|
|
9
9
|
|
|
10
|
-
Архитектура M1:
|
|
11
|
-
(`
|
|
12
|
-
|
|
10
|
+
Архитектура M1: **модель данных** — источник истины, схема привязывает поля к её сигналам
|
|
11
|
+
(`model.$.field`), а сборка идёт ОДНИМ вызовом `createCoreForm` (для рендера ui-kit;
|
|
12
|
+
в рендерерах — `createReactForm` / `createJsonForm`), который создаёт модель, строит форму,
|
|
13
|
+
запускает поведение и собирает валидацию. Layout-схема НЕ несёт валидаторов — валидация живёт в отдельной
|
|
13
14
|
схеме `defineValidationSchema` из `@reformer/core/validation` и запускается внешним
|
|
14
15
|
раннером `validateModel(model, schema)`.
|
|
15
16
|
|
|
16
17
|
```typescript
|
|
17
|
-
import { createModel,
|
|
18
|
+
import { createCoreForm, createModel, useFormBundle, type FormProxy } from '@reformer/core';
|
|
18
19
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
19
20
|
import { required, email } from '@reformer/core/validators';
|
|
20
21
|
import { FormField, Input, Button } from '@reformer/ui-kit';
|
|
@@ -49,8 +50,14 @@ const contactValidation = defineValidationSchema<ContactForm>(({ model }) => {
|
|
|
49
50
|
validate(model.$.email, [required({ message: 'Email is required' }), email({ message: 'Invalid email' })]);
|
|
50
51
|
});
|
|
51
52
|
|
|
52
|
-
// 5.
|
|
53
|
-
|
|
53
|
+
// 5. Сборка ОДНИМ вызовом: модель + ноды поверх её сигналов + валидация.
|
|
54
|
+
// В React оборачивают в useFormBundle — ленивый useState, фабрика зовётся один раз.
|
|
55
|
+
const contact = createCoreForm<ContactForm>({
|
|
56
|
+
model,
|
|
57
|
+
schema: buildSchema, // билдер (model) => tree
|
|
58
|
+
validation: contactValidation,
|
|
59
|
+
});
|
|
60
|
+
const form = contact.form;
|
|
54
61
|
|
|
55
62
|
// 6. Use in React component — thin JSX, FormField does ALL heavy lifting
|
|
56
63
|
function ContactFormComponent() {
|
|
@@ -121,7 +128,7 @@ const schema = {
|
|
|
121
128
|
properties: { array: model.properties, item: propertyItem },
|
|
122
129
|
};
|
|
123
130
|
|
|
124
|
-
const form =
|
|
131
|
+
const { form } = createCoreForm<MyForm>({ model, schema: () => schema });
|
|
125
132
|
|
|
126
133
|
// Операции над массивом — на модели:
|
|
127
134
|
model.properties.push({ type: 'apartment', description: '', estimatedValue: 0 });
|
|
@@ -15,7 +15,7 @@ src/
|
|
|
15
15
|
│
|
|
16
16
|
├── forms/
|
|
17
17
|
│ └── [form-name]/ # Form module — flat, one file per concern
|
|
18
|
-
│ ├── index.tsx # entry + whole form:
|
|
18
|
+
│ ├── index.tsx # entry + whole form: ONE createCoreForm call → <FormWizard> with all steps inline; arrays via FormArraySection
|
|
19
19
|
│ ├── types.ts # form type + enums + { value, label } option type + constant dictionaries
|
|
20
20
|
│ ├── model.ts # createModel + initial values + empty-array-element factories
|
|
21
21
|
│ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
|
|
@@ -60,11 +60,15 @@ export const creditApplicationBehavior = defineFormBehavior<CreditApplicationFor
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
// forms/credit-application/index.tsx — entry: assembles the form, renders <FormWizard> with all steps inline
|
|
63
|
-
import {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
import { createCoreForm } from '@reformer/core';
|
|
64
|
+
// ONE call: model + form + behavior + validation. In the page: useFormBundle(createCreditApplicationForm).
|
|
65
|
+
export const createCreditApplicationForm = () =>
|
|
66
|
+
createCoreForm({
|
|
67
|
+
model: createCreditApplicationModel(),
|
|
68
|
+
schema: creditApplicationSchema, // builder (model) => tree
|
|
69
|
+
behavior: creditApplicationBehavior,
|
|
70
|
+
validation: creditApplicationValidation, // { steps, extras } → bundle.validation
|
|
71
|
+
});
|
|
68
72
|
```
|
|
69
73
|
|
|
70
74
|
### Scaling up: folders (large forms)
|
|
@@ -90,10 +94,10 @@ forms/
|
|
|
90
94
|
│
|
|
91
95
|
├── schema/ # The form definition
|
|
92
96
|
│ ├── model.ts # createModel factory + initial values + array-element factories
|
|
93
|
-
│ ├── schema.ts #
|
|
97
|
+
│ ├── schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
|
|
94
98
|
│ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
|
|
95
99
|
│ ├── behavior.ts # defineFormBehavior(...)
|
|
96
|
-
│ └── create-form.ts # Assembly:
|
|
100
|
+
│ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
|
|
97
101
|
│
|
|
98
102
|
└── components/
|
|
99
103
|
├── steps/ # One component per wizard step
|