@teamnovu/kit-vue-forms 0.2.10 → 0.2.12
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/composables/useField.d.ts +1 -0
- package/dist/composables/useFieldRegistry.d.ts +2 -0
- package/dist/index.js +275 -258
- package/dist/types/validation.d.ts +1 -1
- package/package.json +1 -1
- package/src/composables/useField.ts +5 -0
- package/src/composables/useFieldRegistry.ts +8 -0
- package/src/composables/useForm.ts +10 -4
- package/src/composables/useValidation.ts +6 -1
- package/src/types/validation.ts +1 -1
- package/src/utils/validation.ts +3 -3
- package/tests/useValidation.test.ts +98 -2
|
@@ -10,5 +10,6 @@ export interface UseFieldOptions<T, K extends string> {
|
|
|
10
10
|
existsInForm?: MaybeRef<boolean>;
|
|
11
11
|
onBlur?: () => Awaitable<void>;
|
|
12
12
|
onFocus?: () => Awaitable<void>;
|
|
13
|
+
onChange?: (value: T) => Awaitable<void>;
|
|
13
14
|
}
|
|
14
15
|
export declare function useField<T, K extends string>(fieldOptions: UseFieldOptions<T, K>): FormField<T, K>;
|
|
@@ -8,6 +8,7 @@ export type ResolvedFormField<T, K extends Paths<T>> = FormField<PickProps<T, K>
|
|
|
8
8
|
export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>, 'path'> & {
|
|
9
9
|
onBlur?: () => void;
|
|
10
10
|
onFocus?: () => void;
|
|
11
|
+
onChange?: <T>(value: T) => void;
|
|
11
12
|
};
|
|
12
13
|
interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T> {
|
|
13
14
|
data: T;
|
|
@@ -17,6 +18,7 @@ interface FieldRegistryOptions {
|
|
|
17
18
|
keepValuesOnUnmount?: MaybeRef<boolean>;
|
|
18
19
|
onBlur?: (path: string) => Awaitable<void>;
|
|
19
20
|
onFocus?: (path: string) => Awaitable<void>;
|
|
21
|
+
onChange?: <T>(path: string, value: T) => Awaitable<void>;
|
|
20
22
|
}
|
|
21
23
|
export declare function useFieldRegistry<T extends FormDataDefault, TOut = T>(formState: FormState<T>, validationState: ValidationState<T, TOut>, fieldRegistryOptions?: FieldRegistryOptions): {
|
|
22
24
|
fields: ComputedRef<FieldsTuple<T>>;
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { toValue as
|
|
5
|
-
import { cloneDeep as
|
|
1
|
+
var Y = Object.defineProperty;
|
|
2
|
+
var ee = (r, t, e) => t in r ? Y(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
|
|
3
|
+
var S = (r, t, e) => ee(r, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { toValue as te, toRaw as re, unref as h, shallowRef as $, watch as P, computed as p, isRef as T, reactive as A, toRefs as J, shallowReactive as ae, toRef as j, onScopeDispose as se, triggerRef as ne, ref as G, getCurrentScope as oe, onBeforeUnmount as ie, defineComponent as x, renderSlot as C, normalizeProps as N, guardReactiveProps as W, createBlock as K, openBlock as L, withCtx as U, resolveDynamicComponent as le, mergeProps as ce, createSlots as ue, renderList as de } from "vue";
|
|
5
|
+
import { cloneDeep as fe } from "lodash-es";
|
|
6
6
|
import "zod";
|
|
7
|
-
function
|
|
8
|
-
const t =
|
|
9
|
-
return
|
|
7
|
+
function E(r) {
|
|
8
|
+
const t = te(r), e = re(t);
|
|
9
|
+
return fe(e);
|
|
10
10
|
}
|
|
11
|
-
function
|
|
11
|
+
function he(r) {
|
|
12
12
|
return r.filter(
|
|
13
13
|
(t, e, a) => a.indexOf(t) === e
|
|
14
14
|
);
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function Z(...r) {
|
|
17
17
|
return r.slice(1).reduce((t, e) => {
|
|
18
18
|
if (!t && !e)
|
|
19
19
|
return;
|
|
@@ -23,15 +23,15 @@ function J(...r) {
|
|
|
23
23
|
if (!a)
|
|
24
24
|
return t;
|
|
25
25
|
const s = (t ?? []).concat(e);
|
|
26
|
-
return
|
|
27
|
-
}, r[0]);
|
|
26
|
+
return he(s);
|
|
27
|
+
}, r[0]) ?? [];
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function ve(...r) {
|
|
30
30
|
return r.map((e) => Object.keys(e)).flat().reduce((e, a) => {
|
|
31
|
-
const s = r.map((n) => n[a]).filter(
|
|
31
|
+
const s = r.map((n) => n[a]).filter((n) => !!n);
|
|
32
32
|
return {
|
|
33
33
|
...e,
|
|
34
|
-
[a]:
|
|
34
|
+
[a]: Z(e[a], ...s)
|
|
35
35
|
};
|
|
36
36
|
}, {});
|
|
37
37
|
}
|
|
@@ -44,38 +44,38 @@ function B(...r) {
|
|
|
44
44
|
const t = r[0];
|
|
45
45
|
return r.length === 1 ? t : r.slice(1).reduce(
|
|
46
46
|
(e, a) => ({
|
|
47
|
-
general:
|
|
48
|
-
propertyErrors:
|
|
47
|
+
general: Z(e.general, a.general),
|
|
48
|
+
propertyErrors: ve(e.propertyErrors ?? {}, a.propertyErrors ?? {})
|
|
49
49
|
}),
|
|
50
50
|
t
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
|
-
function
|
|
53
|
+
function q(r) {
|
|
54
54
|
var a;
|
|
55
55
|
const t = (((a = r.general) == null ? void 0 : a.length) ?? 0) > 0, e = Object.entries(r.propertyErrors).filter(([, s]) => s == null ? void 0 : s.length).length > 0;
|
|
56
56
|
return t || e;
|
|
57
57
|
}
|
|
58
|
-
function
|
|
59
|
-
return !
|
|
58
|
+
function z(r) {
|
|
59
|
+
return !q(r.errors);
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function O(r, t) {
|
|
62
62
|
return (e) => async (a) => {
|
|
63
63
|
if (a == null || a.preventDefault(), h(t.validationStrategy) === "none") {
|
|
64
64
|
await e(r.data.value);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
const n = await r.validateForm();
|
|
68
|
-
if (!
|
|
68
|
+
if (!z(n))
|
|
69
69
|
return;
|
|
70
70
|
const u = n.data ?? r.data.value;
|
|
71
71
|
await e(u);
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
class
|
|
74
|
+
class me {
|
|
75
75
|
constructor(t = (e) => e) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
S(this, "weakMap", /* @__PURE__ */ new WeakMap());
|
|
77
|
+
S(this, "map", /* @__PURE__ */ new Map());
|
|
78
|
+
S(this, "hashFn");
|
|
79
79
|
this.hashFn = t;
|
|
80
80
|
}
|
|
81
81
|
isReferenceType(t) {
|
|
@@ -94,13 +94,13 @@ class pe {
|
|
|
94
94
|
this.isReferenceType(a) ? this.weakMap.set(a, e) : this.map.set(a, e);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
function
|
|
97
|
+
function pe(r, t, e) {
|
|
98
98
|
const a = /* @__PURE__ */ new Set();
|
|
99
99
|
return t.map((s, n) => {
|
|
100
|
-
const u = [...r.get(s) ?? []],
|
|
101
|
-
if (
|
|
102
|
-
return a.add(
|
|
103
|
-
id:
|
|
100
|
+
const u = [...r.get(s) ?? []], m = u.findIndex((v) => !a.has(v)), l = (m === -1 ? [] : u.slice(m))[0];
|
|
101
|
+
if (l)
|
|
102
|
+
return a.add(l), {
|
|
103
|
+
id: l,
|
|
104
104
|
item: s,
|
|
105
105
|
path: `${e}.${n}`
|
|
106
106
|
};
|
|
@@ -112,12 +112,12 @@ function ve(r, t, e) {
|
|
|
112
112
|
};
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
const a = new
|
|
117
|
-
return
|
|
115
|
+
function Q(r, t, e) {
|
|
116
|
+
const a = new me(e == null ? void 0 : e.hashFn), s = r.getField(t), n = $([]);
|
|
117
|
+
return P(
|
|
118
118
|
s.data,
|
|
119
|
-
(
|
|
120
|
-
n.value =
|
|
119
|
+
(l) => {
|
|
120
|
+
n.value = pe(a, l, t);
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
immediate: !0,
|
|
@@ -125,56 +125,56 @@ function q(r, t, e) {
|
|
|
125
125
|
}
|
|
126
126
|
), {
|
|
127
127
|
items: n,
|
|
128
|
-
push: (
|
|
128
|
+
push: (l) => {
|
|
129
129
|
const o = s.data.value ?? [];
|
|
130
|
-
return s.setData([...o,
|
|
130
|
+
return s.setData([...o, l]), n.value.at(-1);
|
|
131
131
|
},
|
|
132
|
-
remove: (
|
|
133
|
-
const o = s.data.value ?? [],
|
|
134
|
-
({ id: i }) => i ===
|
|
132
|
+
remove: (l) => {
|
|
133
|
+
const o = s.data.value ?? [], v = n.value.findIndex(
|
|
134
|
+
({ id: i }) => i === l
|
|
135
135
|
);
|
|
136
|
-
|
|
137
|
-
o.slice(0,
|
|
136
|
+
v !== -1 && s.setData(
|
|
137
|
+
o.slice(0, v).concat(o.slice(v + 1))
|
|
138
138
|
);
|
|
139
139
|
},
|
|
140
|
-
insert: (
|
|
141
|
-
const
|
|
140
|
+
insert: (l, o) => {
|
|
141
|
+
const v = s.data.value ?? [];
|
|
142
142
|
return s.setData(
|
|
143
|
-
|
|
143
|
+
v.slice(0, o).concat([l]).concat(v.slice(o))
|
|
144
144
|
), n.value[o];
|
|
145
145
|
},
|
|
146
146
|
field: s
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
-
function
|
|
149
|
+
function H(r) {
|
|
150
150
|
return r === "" ? [] : r.split(/\s*\.\s*/).filter(Boolean);
|
|
151
151
|
}
|
|
152
|
-
function
|
|
153
|
-
const e = Array.isArray(t) ? t :
|
|
152
|
+
function Fe(r, t) {
|
|
153
|
+
const e = Array.isArray(t) ? t : H(t);
|
|
154
154
|
return !!b(r, e.slice(0, -1));
|
|
155
155
|
}
|
|
156
156
|
function b(r, t) {
|
|
157
|
-
return (Array.isArray(t) ? t :
|
|
157
|
+
return (Array.isArray(t) ? t : H(t)).reduce(
|
|
158
158
|
(a, s) => a == null ? void 0 : a[s],
|
|
159
159
|
r
|
|
160
160
|
);
|
|
161
161
|
}
|
|
162
162
|
function ye(r, t, e) {
|
|
163
|
-
const a = Array.isArray(t) ? t :
|
|
163
|
+
const a = Array.isArray(t) ? t : H(t), s = a.at(-1);
|
|
164
164
|
if (s) {
|
|
165
165
|
const n = a.slice(0, -1).reduce(
|
|
166
|
-
(u,
|
|
166
|
+
(u, m) => ((u == null ? void 0 : u[m]) === void 0 && (u[m] = {}), u == null ? void 0 : u[m]),
|
|
167
167
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
168
|
h(r)
|
|
169
169
|
);
|
|
170
170
|
n[s] = e;
|
|
171
171
|
} else {
|
|
172
|
-
if (!
|
|
172
|
+
if (!T(r))
|
|
173
173
|
return;
|
|
174
174
|
r.value = e;
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
const
|
|
177
|
+
const X = (r, t) => p({
|
|
178
178
|
get() {
|
|
179
179
|
return b(h(r), h(t));
|
|
180
180
|
},
|
|
@@ -185,7 +185,7 @@ const Q = (r, t) => m({
|
|
|
185
185
|
function I(r, t) {
|
|
186
186
|
return !r && !t ? "" : !r && t ? t : !t && r ? r : `${r}.${t}`;
|
|
187
187
|
}
|
|
188
|
-
function
|
|
188
|
+
function ge(r, t) {
|
|
189
189
|
if (!t)
|
|
190
190
|
return r;
|
|
191
191
|
const e = `${t}.`, a = Object.fromEntries(
|
|
@@ -202,7 +202,7 @@ function Fe(r, t) {
|
|
|
202
202
|
}
|
|
203
203
|
class Ve {
|
|
204
204
|
constructor(t) {
|
|
205
|
-
|
|
205
|
+
S(this, "rc", 1);
|
|
206
206
|
this.drop = t;
|
|
207
207
|
}
|
|
208
208
|
inc() {
|
|
@@ -212,143 +212,153 @@ class Ve {
|
|
|
212
212
|
this.rc > 0 && (this.rc -= 1, this.rc === 0 && this.drop && this.drop());
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
function
|
|
215
|
+
function we(r) {
|
|
216
216
|
const e = {
|
|
217
217
|
...{
|
|
218
218
|
existsInForm: !0
|
|
219
219
|
},
|
|
220
220
|
...r
|
|
221
|
-
}, a = $(Object.freeze(
|
|
221
|
+
}, a = $(Object.freeze(E(e.initialValue))), s = A({
|
|
222
222
|
value: e.value,
|
|
223
223
|
path: e.path,
|
|
224
224
|
initialValue: a,
|
|
225
225
|
errors: e.errors,
|
|
226
226
|
touched: !1
|
|
227
227
|
});
|
|
228
|
-
|
|
228
|
+
P(
|
|
229
229
|
$(e.initialValue),
|
|
230
230
|
() => {
|
|
231
|
-
a.value = Object.freeze(
|
|
231
|
+
a.value = Object.freeze(E(e.initialValue)), s.value !== h(e.initialValue) && (s.value = E(e.initialValue));
|
|
232
232
|
},
|
|
233
233
|
{ flush: "sync" }
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
s.
|
|
240
|
-
},
|
|
241
|
-
var
|
|
242
|
-
(
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
234
|
+
), P(() => s.value, (d) => {
|
|
235
|
+
var D;
|
|
236
|
+
(D = r.onChange) == null || D.call(r, d);
|
|
237
|
+
}, { deep: !0 });
|
|
238
|
+
const n = p(() => JSON.stringify(s.value) !== JSON.stringify(s.initialValue)), u = (d) => {
|
|
239
|
+
s.value = d;
|
|
240
|
+
}, m = () => {
|
|
241
|
+
var d;
|
|
242
|
+
s.touched = !0, s.errors = [], (d = e.onBlur) == null || d.call(e);
|
|
243
|
+
}, y = () => {
|
|
244
|
+
var d;
|
|
245
|
+
(d = e.onFocus) == null || d.call(e);
|
|
246
|
+
}, l = () => {
|
|
247
|
+
const d = s.path.split(".").at(-1) || "";
|
|
248
|
+
h(e.existsInForm) && !/^\d+$/.test(d) && (s.value = E(s.initialValue)), s.touched = !1, s.errors = [];
|
|
249
|
+
}, o = (d) => {
|
|
250
|
+
n.value || u(E(d)), s.initialValue = d;
|
|
251
|
+
}, v = (d) => {
|
|
252
|
+
s.errors = d;
|
|
250
253
|
}, i = () => {
|
|
251
254
|
s.errors = [];
|
|
252
|
-
},
|
|
255
|
+
}, c = J(s);
|
|
253
256
|
return {
|
|
254
|
-
data:
|
|
255
|
-
path:
|
|
256
|
-
initialValue:
|
|
257
|
-
errors:
|
|
258
|
-
touched:
|
|
257
|
+
data: c.value,
|
|
258
|
+
path: c.path,
|
|
259
|
+
initialValue: c.initialValue,
|
|
260
|
+
errors: c.errors,
|
|
261
|
+
touched: c.touched,
|
|
259
262
|
dirty: n,
|
|
260
263
|
setData: u,
|
|
261
264
|
setInitialData: o,
|
|
262
|
-
onBlur:
|
|
263
|
-
onFocus:
|
|
264
|
-
reset:
|
|
265
|
-
setErrors:
|
|
265
|
+
onBlur: m,
|
|
266
|
+
onFocus: y,
|
|
267
|
+
reset: l,
|
|
268
|
+
setErrors: v,
|
|
266
269
|
clearErrors: i
|
|
267
270
|
};
|
|
268
271
|
}
|
|
269
|
-
const
|
|
272
|
+
const Ee = {
|
|
270
273
|
keepValuesOnUnmount: !0
|
|
271
274
|
};
|
|
272
|
-
function
|
|
275
|
+
function De(r, t) {
|
|
273
276
|
const e = () => b(r.initialData, t), a = $(e());
|
|
274
|
-
return
|
|
277
|
+
return P(
|
|
275
278
|
() => r.initialData,
|
|
276
279
|
() => {
|
|
277
|
-
a.value = e(),
|
|
280
|
+
a.value = e(), ne(a);
|
|
278
281
|
},
|
|
279
282
|
{ flush: "sync" }
|
|
280
283
|
), a;
|
|
281
284
|
}
|
|
282
285
|
function Re(r, t, e) {
|
|
283
|
-
const a = /* @__PURE__ */ new Map(), s =
|
|
284
|
-
...
|
|
286
|
+
const a = /* @__PURE__ */ new Map(), s = ae(/* @__PURE__ */ new Map()), n = {
|
|
287
|
+
...Ee,
|
|
285
288
|
...e
|
|
286
289
|
}, u = (i) => {
|
|
287
|
-
const
|
|
288
|
-
s.set(
|
|
289
|
-
},
|
|
290
|
-
var
|
|
291
|
-
n != null && n.keepValuesOnUnmount || (
|
|
292
|
-
},
|
|
293
|
-
var
|
|
294
|
-
a.has(i) ? (
|
|
295
|
-
},
|
|
296
|
-
var
|
|
297
|
-
a.has(i) && ((
|
|
290
|
+
const c = h(i.path);
|
|
291
|
+
s.set(c, i);
|
|
292
|
+
}, m = (i) => {
|
|
293
|
+
var c;
|
|
294
|
+
n != null && n.keepValuesOnUnmount || (c = s.get(i)) == null || c.reset(), s.delete(i);
|
|
295
|
+
}, y = (i) => {
|
|
296
|
+
var c;
|
|
297
|
+
a.has(i) ? (c = a.get(i)) == null || c.inc() : a.set(i, new Ve(() => m(i)));
|
|
298
|
+
}, l = (i) => {
|
|
299
|
+
var c;
|
|
300
|
+
a.has(i) && ((c = a.get(i)) == null || c.dec());
|
|
298
301
|
}, o = (i) => {
|
|
299
|
-
const { path:
|
|
300
|
-
if (!s.has(
|
|
301
|
-
const
|
|
302
|
-
path:
|
|
303
|
-
value:
|
|
304
|
-
initialValue:
|
|
305
|
-
existsInForm:
|
|
306
|
-
errors:
|
|
302
|
+
const { path: c } = i;
|
|
303
|
+
if (!s.has(c)) {
|
|
304
|
+
const D = we({
|
|
305
|
+
path: c,
|
|
306
|
+
value: X(j(r, "data"), c),
|
|
307
|
+
initialValue: De(r, c),
|
|
308
|
+
existsInForm: p(() => Fe(r.data, h(c))),
|
|
309
|
+
errors: p({
|
|
307
310
|
get() {
|
|
308
|
-
return t.errors.value.propertyErrors[
|
|
311
|
+
return t.errors.value.propertyErrors[c] || [];
|
|
309
312
|
},
|
|
310
|
-
set(
|
|
311
|
-
t.errors.value.propertyErrors[
|
|
313
|
+
set(V) {
|
|
314
|
+
t.errors.value.propertyErrors[c] = V;
|
|
312
315
|
}
|
|
313
316
|
}),
|
|
314
317
|
onBlur: async () => {
|
|
315
|
-
var
|
|
318
|
+
var V, w;
|
|
316
319
|
await Promise.all([
|
|
317
|
-
(
|
|
318
|
-
(
|
|
320
|
+
(V = n == null ? void 0 : n.onBlur) == null ? void 0 : V.call(n, h(c)),
|
|
321
|
+
(w = i.onBlur) == null ? void 0 : w.call(i)
|
|
319
322
|
]);
|
|
320
323
|
},
|
|
321
324
|
onFocus: async () => {
|
|
322
|
-
var
|
|
325
|
+
var V, w;
|
|
323
326
|
await Promise.all([
|
|
324
|
-
(
|
|
325
|
-
(
|
|
327
|
+
(V = n == null ? void 0 : n.onFocus) == null ? void 0 : V.call(n, h(c)),
|
|
328
|
+
(w = i.onFocus) == null ? void 0 : w.call(i)
|
|
329
|
+
]);
|
|
330
|
+
},
|
|
331
|
+
onChange: async (V) => {
|
|
332
|
+
var w, M;
|
|
333
|
+
await Promise.all([
|
|
334
|
+
(w = n == null ? void 0 : n.onChange) == null ? void 0 : w.call(n, h(c), V),
|
|
335
|
+
(M = i.onChange) == null ? void 0 : M.call(i, V)
|
|
326
336
|
]);
|
|
327
337
|
}
|
|
328
338
|
});
|
|
329
|
-
u(
|
|
339
|
+
u(D);
|
|
330
340
|
}
|
|
331
|
-
const
|
|
332
|
-
return
|
|
333
|
-
c
|
|
334
|
-
}),
|
|
335
|
-
},
|
|
341
|
+
const d = s.get(c);
|
|
342
|
+
return y(c), se(() => {
|
|
343
|
+
l(c);
|
|
344
|
+
}), d;
|
|
345
|
+
}, v = (i) => o(i);
|
|
336
346
|
return {
|
|
337
|
-
fields:
|
|
347
|
+
fields: p(() => [...s.values()]),
|
|
338
348
|
getField: (i) => o({ path: i }),
|
|
339
349
|
registerField: u,
|
|
340
|
-
deregisterField:
|
|
341
|
-
defineField:
|
|
350
|
+
deregisterField: m,
|
|
351
|
+
defineField: v
|
|
342
352
|
};
|
|
343
353
|
}
|
|
344
|
-
function
|
|
345
|
-
const t =
|
|
354
|
+
function Pe(r) {
|
|
355
|
+
const t = p(() => r.fields.value.some((a) => h(a.dirty))), e = p(() => r.fields.value.some((a) => h(a.touched)));
|
|
346
356
|
return {
|
|
347
357
|
isDirty: t,
|
|
348
358
|
isTouched: e
|
|
349
359
|
};
|
|
350
360
|
}
|
|
351
|
-
function
|
|
361
|
+
function Se(r) {
|
|
352
362
|
const t = r.issues.filter((a) => a.path.length === 0).map((a) => a.message), e = r.issues.filter((a) => a.path.length > 0).reduce((a, s) => {
|
|
353
363
|
const n = s.path.join(".");
|
|
354
364
|
return {
|
|
@@ -361,23 +371,23 @@ function Pe(r) {
|
|
|
361
371
|
propertyErrors: e
|
|
362
372
|
};
|
|
363
373
|
}
|
|
364
|
-
const
|
|
374
|
+
const g = {
|
|
365
375
|
errors: {
|
|
366
376
|
general: [],
|
|
367
377
|
propertyErrors: {}
|
|
368
378
|
}
|
|
369
379
|
};
|
|
370
|
-
class
|
|
380
|
+
class be {
|
|
371
381
|
constructor(t) {
|
|
372
382
|
this.schema = t;
|
|
373
383
|
}
|
|
374
384
|
async validate(t) {
|
|
375
385
|
if (!this.schema)
|
|
376
|
-
return
|
|
386
|
+
return g;
|
|
377
387
|
const e = await this.schema.safeParseAsync(t);
|
|
378
388
|
if (e.success)
|
|
379
|
-
return
|
|
380
|
-
const a =
|
|
389
|
+
return g;
|
|
390
|
+
const a = Se(e.error);
|
|
381
391
|
return {
|
|
382
392
|
data: e.data,
|
|
383
393
|
errors: {
|
|
@@ -387,16 +397,16 @@ class Se {
|
|
|
387
397
|
};
|
|
388
398
|
}
|
|
389
399
|
}
|
|
390
|
-
class
|
|
400
|
+
class Ie {
|
|
391
401
|
constructor(t) {
|
|
392
402
|
this.validateFn = t;
|
|
393
403
|
}
|
|
394
404
|
async validate(t) {
|
|
395
405
|
if (!this.validateFn)
|
|
396
|
-
return
|
|
406
|
+
return g;
|
|
397
407
|
try {
|
|
398
408
|
const e = await this.validateFn(t);
|
|
399
|
-
return
|
|
409
|
+
return z(e) ? g : e;
|
|
400
410
|
} catch (e) {
|
|
401
411
|
return {
|
|
402
412
|
errors: {
|
|
@@ -407,11 +417,11 @@ class be {
|
|
|
407
417
|
}
|
|
408
418
|
}
|
|
409
419
|
}
|
|
410
|
-
class
|
|
420
|
+
class $e {
|
|
411
421
|
constructor(t, e) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
this.schema = t, this.validateFn = e, this.schemaValidator = new
|
|
422
|
+
S(this, "schemaValidator");
|
|
423
|
+
S(this, "functionValidator");
|
|
424
|
+
this.schema = t, this.validateFn = e, this.schemaValidator = new be(this.schema), this.functionValidator = new Ie(this.validateFn);
|
|
415
425
|
}
|
|
416
426
|
async validate(t) {
|
|
417
427
|
const [e, a] = await Promise.all([
|
|
@@ -424,62 +434,62 @@ class Ie {
|
|
|
424
434
|
};
|
|
425
435
|
}
|
|
426
436
|
}
|
|
427
|
-
function
|
|
428
|
-
return
|
|
437
|
+
function _(r) {
|
|
438
|
+
return p(() => new $e(
|
|
429
439
|
h(r.schema),
|
|
430
440
|
h(r.validateFn)
|
|
431
441
|
));
|
|
432
442
|
}
|
|
433
|
-
function
|
|
443
|
+
function Ce(r, t) {
|
|
434
444
|
const e = A({
|
|
435
|
-
validators:
|
|
445
|
+
validators: G([_(t)]),
|
|
436
446
|
isValidated: !1,
|
|
437
|
-
errors: h(t.errors) ??
|
|
438
|
-
}), a = (o =
|
|
439
|
-
e.errors = B(h(t.errors) ??
|
|
447
|
+
errors: h(t.errors) ?? g.errors
|
|
448
|
+
}), a = (o = g.errors) => {
|
|
449
|
+
e.errors = B(h(t.errors) ?? g.errors, o);
|
|
440
450
|
};
|
|
441
|
-
|
|
451
|
+
P(() => h(t.errors), async () => {
|
|
442
452
|
if (e.isValidated) {
|
|
443
453
|
const o = await n();
|
|
444
454
|
a(o.errors);
|
|
445
455
|
} else
|
|
446
456
|
a();
|
|
447
|
-
}, { immediate: !0 }),
|
|
457
|
+
}, { immediate: !0 }), P(
|
|
448
458
|
[() => e.validators],
|
|
449
459
|
async (o) => {
|
|
450
460
|
if (e.isValidated)
|
|
451
461
|
if (o) {
|
|
452
|
-
const
|
|
453
|
-
e.errors =
|
|
462
|
+
const v = await n();
|
|
463
|
+
e.errors = v.errors;
|
|
454
464
|
} else
|
|
455
|
-
e.errors =
|
|
465
|
+
e.errors = g.errors;
|
|
456
466
|
},
|
|
457
467
|
{ immediate: !0 }
|
|
458
|
-
),
|
|
468
|
+
), P([() => r.data, () => h(t.schema)], () => {
|
|
459
469
|
e.isValidated && u();
|
|
460
470
|
});
|
|
461
471
|
const s = (o) => {
|
|
462
|
-
const
|
|
463
|
-
return e.validators.push(
|
|
472
|
+
const v = T(o) ? o : _(o);
|
|
473
|
+
return e.validators.push(v), oe() && ie(() => {
|
|
464
474
|
e.validators = e.validators.filter(
|
|
465
|
-
(i) => i !==
|
|
475
|
+
(i) => i !== v
|
|
466
476
|
);
|
|
467
|
-
}),
|
|
477
|
+
}), v;
|
|
468
478
|
};
|
|
469
479
|
async function n() {
|
|
470
|
-
var
|
|
480
|
+
var c;
|
|
471
481
|
const o = await Promise.all(
|
|
472
|
-
e.validators.filter((
|
|
473
|
-
),
|
|
474
|
-
let { errors: i } =
|
|
475
|
-
if (!
|
|
476
|
-
const
|
|
477
|
-
i = B(...
|
|
482
|
+
e.validators.filter((d) => h(d) !== void 0).map((d) => h(d).validate(r.data))
|
|
483
|
+
), v = o.every((d) => z(d));
|
|
484
|
+
let { errors: i } = g;
|
|
485
|
+
if (!v) {
|
|
486
|
+
const d = o.map((D) => D.errors);
|
|
487
|
+
i = B(...d);
|
|
478
488
|
}
|
|
479
489
|
return {
|
|
480
490
|
errors: i,
|
|
481
491
|
// TODO: Implement data disambiguation strategy
|
|
482
|
-
data: (
|
|
492
|
+
data: (c = o.findLast((d) => !!d.data)) == null ? void 0 : c.data
|
|
483
493
|
};
|
|
484
494
|
}
|
|
485
495
|
const u = async () => {
|
|
@@ -487,37 +497,40 @@ function $e(r, t) {
|
|
|
487
497
|
return a(o.errors), e.isValidated = !0, {
|
|
488
498
|
errors: e.errors
|
|
489
499
|
};
|
|
490
|
-
},
|
|
491
|
-
|
|
500
|
+
}, m = async (o) => {
|
|
501
|
+
if (!e.isValidated)
|
|
502
|
+
return g;
|
|
503
|
+
const v = await n();
|
|
492
504
|
return a({
|
|
493
|
-
general:
|
|
505
|
+
general: v.errors.general,
|
|
494
506
|
propertyErrors: {
|
|
495
|
-
|
|
507
|
+
...e.errors.propertyErrors,
|
|
508
|
+
[o]: v.errors.propertyErrors[o] ?? []
|
|
496
509
|
}
|
|
497
510
|
}), {
|
|
498
|
-
data:
|
|
511
|
+
data: v.data,
|
|
499
512
|
errors: e.errors
|
|
500
513
|
};
|
|
501
|
-
},
|
|
502
|
-
e.isValidated = !1, e.errors = h(t.errors) ??
|
|
514
|
+
}, y = p(() => !q(e.errors)), l = () => {
|
|
515
|
+
e.isValidated = !1, e.errors = h(t.errors) ?? g.errors;
|
|
503
516
|
};
|
|
504
517
|
return {
|
|
505
|
-
...
|
|
518
|
+
...J(e),
|
|
506
519
|
validateForm: u,
|
|
507
|
-
validateField:
|
|
520
|
+
validateField: m,
|
|
508
521
|
defineValidator: s,
|
|
509
|
-
isValid:
|
|
510
|
-
reset:
|
|
522
|
+
isValid: y,
|
|
523
|
+
reset: l
|
|
511
524
|
};
|
|
512
525
|
}
|
|
513
|
-
class
|
|
526
|
+
class Ae {
|
|
514
527
|
constructor(t, e) {
|
|
515
528
|
this.path = t, this.validator = e;
|
|
516
529
|
}
|
|
517
530
|
async validate(t) {
|
|
518
531
|
const e = b(t, this.path);
|
|
519
532
|
if (!this.validator)
|
|
520
|
-
return
|
|
533
|
+
return g;
|
|
521
534
|
const a = await this.validator.validate(e);
|
|
522
535
|
return {
|
|
523
536
|
errors: {
|
|
@@ -531,97 +544,100 @@ class ke {
|
|
|
531
544
|
};
|
|
532
545
|
}
|
|
533
546
|
}
|
|
534
|
-
function
|
|
535
|
-
const s =
|
|
536
|
-
...
|
|
537
|
-
path:
|
|
538
|
-
setData: (
|
|
539
|
-
|
|
547
|
+
function Me(r, t, e, a) {
|
|
548
|
+
const s = X(r.data, t), n = p(() => b(r.initialData.value, t)), u = (f) => ({
|
|
549
|
+
...f,
|
|
550
|
+
path: p(() => h(f.path).replace(t + ".", "")),
|
|
551
|
+
setData: (F) => {
|
|
552
|
+
f.setData(F);
|
|
540
553
|
}
|
|
541
|
-
}),
|
|
542
|
-
const
|
|
543
|
-
return
|
|
544
|
-
},
|
|
545
|
-
const
|
|
546
|
-
...
|
|
547
|
-
path:
|
|
554
|
+
}), m = (f) => {
|
|
555
|
+
const F = I(t, f), R = r.getField(F);
|
|
556
|
+
return R ? u(R) : {};
|
|
557
|
+
}, y = (f) => {
|
|
558
|
+
const F = I(t, f.path), R = r.defineField({
|
|
559
|
+
...f,
|
|
560
|
+
path: F
|
|
548
561
|
});
|
|
549
|
-
return u(
|
|
550
|
-
},
|
|
551
|
-
const
|
|
552
|
-
return
|
|
553
|
-
}).map((
|
|
554
|
-
const
|
|
555
|
-
return
|
|
556
|
-
}),
|
|
562
|
+
return u(R);
|
|
563
|
+
}, l = p(() => r.fields.value.filter((f) => {
|
|
564
|
+
const F = f.path.value;
|
|
565
|
+
return F.startsWith(t + ".") || F === t;
|
|
566
|
+
}).map((f) => u(f))), o = () => r.fields.value.filter((f) => {
|
|
567
|
+
const F = f.path.value;
|
|
568
|
+
return F.startsWith(t + ".") || F === t;
|
|
569
|
+
}), v = p(() => o().some((f) => f.dirty.value)), i = p(() => o().some((f) => f.touched.value)), c = p(() => r.isValid.value), d = p(() => r.isValidated.value), D = p(() => ge(h(r.errors), t)), k = {
|
|
557
570
|
data: s,
|
|
558
|
-
fields:
|
|
571
|
+
fields: l,
|
|
559
572
|
initialData: n,
|
|
560
|
-
defineField:
|
|
561
|
-
getField:
|
|
562
|
-
isDirty:
|
|
573
|
+
defineField: y,
|
|
574
|
+
getField: m,
|
|
575
|
+
isDirty: v,
|
|
563
576
|
isTouched: i,
|
|
564
|
-
isValid:
|
|
565
|
-
isValidated:
|
|
566
|
-
errors:
|
|
567
|
-
defineValidator: (
|
|
568
|
-
const
|
|
569
|
-
() => new
|
|
577
|
+
isValid: c,
|
|
578
|
+
isValidated: d,
|
|
579
|
+
errors: D,
|
|
580
|
+
defineValidator: (f) => {
|
|
581
|
+
const F = T(f) ? f : _(f), R = p(
|
|
582
|
+
() => new Ae(t, h(F))
|
|
570
583
|
);
|
|
571
|
-
return r.defineValidator(
|
|
584
|
+
return r.defineValidator(R), F;
|
|
572
585
|
},
|
|
573
|
-
reset: () => o().forEach((
|
|
586
|
+
reset: () => o().forEach((f) => f.reset()),
|
|
574
587
|
validateForm: () => r.validateForm(),
|
|
575
|
-
getSubForm: (
|
|
576
|
-
const
|
|
588
|
+
getSubForm: (f, F) => {
|
|
589
|
+
const R = I(t, f);
|
|
577
590
|
return r.getSubForm(
|
|
578
|
-
|
|
579
|
-
|
|
591
|
+
R,
|
|
592
|
+
F
|
|
580
593
|
);
|
|
581
594
|
},
|
|
582
|
-
submitHandler: (
|
|
583
|
-
getFieldArray: (
|
|
595
|
+
submitHandler: (f) => O(k, e ?? {})(f),
|
|
596
|
+
getFieldArray: (f, F) => Q(k, f, F)
|
|
584
597
|
};
|
|
585
|
-
return
|
|
598
|
+
return k;
|
|
586
599
|
}
|
|
587
600
|
function xe(r) {
|
|
588
|
-
const t =
|
|
601
|
+
const t = p(() => E(r.initialData)), e = G(E(t)), a = A({
|
|
589
602
|
initialData: t,
|
|
590
603
|
data: e
|
|
591
604
|
});
|
|
592
|
-
|
|
605
|
+
P(
|
|
593
606
|
t,
|
|
594
|
-
(
|
|
595
|
-
a.data =
|
|
607
|
+
(l) => {
|
|
608
|
+
a.data = E(l);
|
|
596
609
|
},
|
|
597
610
|
{ flush: "sync" }
|
|
598
611
|
);
|
|
599
|
-
const s =
|
|
612
|
+
const s = Ce(a, r), n = Re(a, s, {
|
|
600
613
|
keepValuesOnUnmount: r.keepValuesOnUnmount,
|
|
601
|
-
onBlur: async (
|
|
602
|
-
h(r.validationStrategy) === "onTouch" && s.validateField(
|
|
614
|
+
onBlur: async (l) => {
|
|
615
|
+
h(r.validationStrategy) === "onTouch" && s.validateField(l);
|
|
616
|
+
},
|
|
617
|
+
onChange: async (l) => {
|
|
618
|
+
h(r.validationStrategy) === "onDataChange" && s.validateField(l);
|
|
603
619
|
}
|
|
604
|
-
}), u =
|
|
605
|
-
e.value =
|
|
606
|
-
for (const
|
|
607
|
-
|
|
620
|
+
}), u = Pe(n), m = () => {
|
|
621
|
+
e.value = E(t), s.reset();
|
|
622
|
+
for (const l of n.fields.value)
|
|
623
|
+
l.reset();
|
|
608
624
|
};
|
|
609
625
|
h(r.validationStrategy) === "onFormOpen" && s.validateForm();
|
|
610
|
-
const
|
|
626
|
+
const y = {
|
|
611
627
|
...n,
|
|
612
628
|
...s,
|
|
613
629
|
...u,
|
|
614
|
-
reset:
|
|
630
|
+
reset: m,
|
|
615
631
|
initialData: j(a, "initialData"),
|
|
616
632
|
data: j(a, "data"),
|
|
617
633
|
validateForm: s.validateForm,
|
|
618
|
-
submitHandler: (
|
|
619
|
-
getSubForm: (
|
|
620
|
-
getFieldArray: (
|
|
634
|
+
submitHandler: (l) => O(y, r)(l),
|
|
635
|
+
getSubForm: (l, o) => Me(y, l, r),
|
|
636
|
+
getFieldArray: (l, o) => Q(y, l, o)
|
|
621
637
|
};
|
|
622
|
-
return
|
|
638
|
+
return y;
|
|
623
639
|
}
|
|
624
|
-
const
|
|
640
|
+
const ke = /* @__PURE__ */ x({
|
|
625
641
|
__name: "Field",
|
|
626
642
|
props: {
|
|
627
643
|
form: {},
|
|
@@ -631,15 +647,16 @@ const Me = /* @__PURE__ */ T({
|
|
|
631
647
|
errors: {},
|
|
632
648
|
existsInForm: {},
|
|
633
649
|
onBlur: { type: Function },
|
|
634
|
-
onFocus: { type: Function }
|
|
650
|
+
onFocus: { type: Function },
|
|
651
|
+
onChange: { type: Function }
|
|
635
652
|
},
|
|
636
653
|
setup(r) {
|
|
637
654
|
const t = r, e = t.form.defineField({
|
|
638
655
|
path: t.path
|
|
639
656
|
}), a = A(e);
|
|
640
|
-
return (s, n) =>
|
|
657
|
+
return (s, n) => C(s.$slots, "default", N(W(a)));
|
|
641
658
|
}
|
|
642
|
-
}),
|
|
659
|
+
}), Ne = /* @__PURE__ */ x({
|
|
643
660
|
inheritAttrs: !1,
|
|
644
661
|
__name: "FormFieldWrapper",
|
|
645
662
|
props: {
|
|
@@ -649,28 +666,28 @@ const Me = /* @__PURE__ */ T({
|
|
|
649
666
|
path: {}
|
|
650
667
|
},
|
|
651
668
|
setup(r) {
|
|
652
|
-
return (t, e) => (
|
|
669
|
+
return (t, e) => (L(), K(ke, {
|
|
653
670
|
form: r.form,
|
|
654
671
|
path: r.path
|
|
655
672
|
}, {
|
|
656
|
-
default: U(({ errors: a, data: s, setData: n, onBlur: u, onFocus:
|
|
657
|
-
(
|
|
673
|
+
default: U(({ errors: a, data: s, setData: n, onBlur: u, onFocus: m }) => [
|
|
674
|
+
(L(), K(le(r.component), ce({ ...r.componentProps, ...t.$attrs }, {
|
|
658
675
|
"on-blur": u,
|
|
659
|
-
"on-focus":
|
|
676
|
+
"on-focus": m,
|
|
660
677
|
"model-value": s,
|
|
661
678
|
errors: a,
|
|
662
679
|
name: r.path,
|
|
663
680
|
"onUpdate:modelValue": n
|
|
664
|
-
}),
|
|
681
|
+
}), ue({
|
|
665
682
|
default: U(() => [
|
|
666
|
-
|
|
683
|
+
C(t.$slots, "default")
|
|
667
684
|
]),
|
|
668
685
|
_: 2
|
|
669
686
|
}, [
|
|
670
|
-
|
|
671
|
-
name:
|
|
687
|
+
de(t.$slots, (y, l) => ({
|
|
688
|
+
name: l,
|
|
672
689
|
fn: U((o) => [
|
|
673
|
-
|
|
690
|
+
C(t.$slots, l, N(W(o ?? {})))
|
|
674
691
|
])
|
|
675
692
|
}))
|
|
676
693
|
]), 1040, ["on-blur", "on-focus", "model-value", "errors", "name", "onUpdate:modelValue"]))
|
|
@@ -678,20 +695,20 @@ const Me = /* @__PURE__ */ T({
|
|
|
678
695
|
_: 3
|
|
679
696
|
}, 8, ["form", "path"]));
|
|
680
697
|
}
|
|
681
|
-
}),
|
|
698
|
+
}), We = /* @__PURE__ */ x({
|
|
682
699
|
__name: "FormPart",
|
|
683
700
|
props: {
|
|
684
701
|
form: {},
|
|
685
702
|
path: {}
|
|
686
703
|
},
|
|
687
704
|
setup(r) {
|
|
688
|
-
const t = r, e =
|
|
689
|
-
return (a, s) =>
|
|
705
|
+
const t = r, e = p(() => t.form.getSubForm(t.path));
|
|
706
|
+
return (a, s) => C(a.$slots, "default", N(W({ subform: e.value })));
|
|
690
707
|
}
|
|
691
708
|
});
|
|
692
709
|
export {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
710
|
+
ke as Field,
|
|
711
|
+
Ne as FormFieldWrapper,
|
|
712
|
+
We as FormPart,
|
|
696
713
|
xe as useForm
|
|
697
714
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FormDataDefault } from './form';
|
|
2
|
-
export type ValidationStrategy = 'onTouch' | 'onFormOpen' | 'none' | 'preSubmit';
|
|
2
|
+
export type ValidationStrategy = 'onTouch' | 'onFormOpen' | 'none' | 'preSubmit' | 'onDataChange';
|
|
3
3
|
export type ValidationErrorMessage = string;
|
|
4
4
|
export type ValidationErrors = ValidationErrorMessage[] | undefined;
|
|
5
5
|
export interface ErrorBag {
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ export interface UseFieldOptions<T, K extends string> {
|
|
|
12
12
|
existsInForm?: MaybeRef<boolean>
|
|
13
13
|
onBlur?: () => Awaitable<void>
|
|
14
14
|
onFocus?: () => Awaitable<void>
|
|
15
|
+
onChange?: (value: T) => Awaitable<void>
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function useField<T, K extends string>(fieldOptions: UseFieldOptions<T, K>): FormField<T, K> {
|
|
@@ -44,6 +45,10 @@ export function useField<T, K extends string>(fieldOptions: UseFieldOptions<T, K
|
|
|
44
45
|
{ flush: 'sync' },
|
|
45
46
|
)
|
|
46
47
|
|
|
48
|
+
watch(() => state.value, (newData) => {
|
|
49
|
+
fieldOptions.onChange?.(newData as T)
|
|
50
|
+
}, { deep: true })
|
|
51
|
+
|
|
47
52
|
const dirty = computed(() => {
|
|
48
53
|
return JSON.stringify(state.value) !== JSON.stringify(state.initialValue)
|
|
49
54
|
})
|
|
@@ -31,6 +31,7 @@ export type DefineFieldOptions<F, K extends string> = Pick<
|
|
|
31
31
|
> & {
|
|
32
32
|
onBlur?: () => void
|
|
33
33
|
onFocus?: () => void
|
|
34
|
+
onChange?: <T>(value: T) => void
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
interface FormState<
|
|
@@ -45,6 +46,7 @@ interface FieldRegistryOptions {
|
|
|
45
46
|
keepValuesOnUnmount?: MaybeRef<boolean>
|
|
46
47
|
onBlur?: (path: string) => Awaitable<void>
|
|
47
48
|
onFocus?: (path: string) => Awaitable<void>
|
|
49
|
+
onChange?: <T>(path: string, value: T) => Awaitable<void>
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const optionDefaults = {
|
|
@@ -143,6 +145,12 @@ export function useFieldRegistry<T extends FormDataDefault, TOut = T>(
|
|
|
143
145
|
options.onFocus?.(),
|
|
144
146
|
])
|
|
145
147
|
},
|
|
148
|
+
onChange: async (value: PickProps<T, K>) => {
|
|
149
|
+
await Promise.all([
|
|
150
|
+
registryOptions?.onChange?.(unref(path), value),
|
|
151
|
+
options.onChange?.(value),
|
|
152
|
+
])
|
|
153
|
+
},
|
|
146
154
|
})
|
|
147
155
|
|
|
148
156
|
registerField(field)
|
|
@@ -30,10 +30,11 @@ export interface UseFormOptions<T extends FormDataDefault, TOut = T>
|
|
|
30
30
|
/* eslint-disable no-redeclare */
|
|
31
31
|
// Overload: with schema - infer types from schema
|
|
32
32
|
// initialData can be partial, but provided fields must match schema types
|
|
33
|
-
export function useForm<
|
|
34
|
-
T
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
export function useForm<T extends FormDataDefault, TOut = T>(
|
|
34
|
+
options: UseFormOptions<T, TOut> & {
|
|
35
|
+
schema: MaybeRef<z.ZodType<TOut, unknown>>
|
|
36
|
+
},
|
|
37
|
+
): Form<T, TOut>
|
|
37
38
|
|
|
38
39
|
// Overload: without schema - infer types from initialData
|
|
39
40
|
export function useForm<T extends FormDataDefault>(
|
|
@@ -70,6 +71,11 @@ export function useForm<T extends FormDataDefault, TOut = T>(
|
|
|
70
71
|
validationState.validateField(path)
|
|
71
72
|
}
|
|
72
73
|
},
|
|
74
|
+
onChange: async (path: string) => {
|
|
75
|
+
if (unref(options.validationStrategy) === 'onDataChange') {
|
|
76
|
+
validationState.validateField(path)
|
|
77
|
+
}
|
|
78
|
+
},
|
|
73
79
|
})
|
|
74
80
|
const formState = useFormState(fieldRegistry)
|
|
75
81
|
|
|
@@ -214,12 +214,17 @@ export function useValidation<T extends FormDataDefault, TOut = T>(
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const validateField = async (path: string): Promise<ValidationResult<TOut>> => {
|
|
217
|
+
if (!validationState.isValidated) {
|
|
218
|
+
return SuccessValidationResult
|
|
219
|
+
}
|
|
220
|
+
|
|
217
221
|
const validationResults = await getValidationResults()
|
|
218
222
|
|
|
219
223
|
updateErrors({
|
|
220
224
|
general: validationResults.errors.general,
|
|
221
225
|
propertyErrors: {
|
|
222
|
-
|
|
226
|
+
...validationState.errors.propertyErrors,
|
|
227
|
+
[path]: validationResults.errors.propertyErrors[path] ?? [],
|
|
223
228
|
},
|
|
224
229
|
})
|
|
225
230
|
|
package/src/types/validation.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FormDataDefault } from './form'
|
|
2
2
|
|
|
3
|
-
export type ValidationStrategy = 'onTouch' | 'onFormOpen' | 'none' | 'preSubmit'
|
|
3
|
+
export type ValidationStrategy = 'onTouch' | 'onFormOpen' | 'none' | 'preSubmit' | 'onDataChange'
|
|
4
4
|
|
|
5
5
|
export type ValidationErrorMessage = string
|
|
6
6
|
export type ValidationErrors = ValidationErrorMessage[] | undefined
|
package/src/utils/validation.ts
CHANGED
|
@@ -20,18 +20,18 @@ function mergeErrorMessages(...msgs: ValidationErrors[]) {
|
|
|
20
20
|
}
|
|
21
21
|
const allMessages = (acc ?? []).concat(msg!)
|
|
22
22
|
return deduplicate(allMessages)
|
|
23
|
-
}, msgs[0] as ValidationErrors)
|
|
23
|
+
}, msgs[0] as ValidationErrors) ?? []
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function mergePropertyErrors(...propertyErrors: Record<string, ValidationErrors>[]): Record<string, ValidationErrors> {
|
|
27
27
|
const allKeys = propertyErrors.map(errs => Object.keys(errs)).flat()
|
|
28
28
|
|
|
29
29
|
return allKeys.reduce((acc, key) => {
|
|
30
|
-
const values = propertyErrors.map(errs => errs[key]).filter(
|
|
30
|
+
const values = propertyErrors.map(errs => errs[key]).filter((v): v is NonNullable<typeof v> => !!v)
|
|
31
31
|
|
|
32
32
|
return {
|
|
33
33
|
...acc,
|
|
34
|
-
[key]: mergeErrorMessages(...values),
|
|
34
|
+
[key]: mergeErrorMessages(acc[key], ...values),
|
|
35
35
|
}
|
|
36
36
|
}, {} as Record<string, ValidationErrors>)
|
|
37
37
|
}
|
|
@@ -280,6 +280,30 @@ describe('useValidation', () => {
|
|
|
280
280
|
expect(validation.errors.value).toEqual(SuccessValidationResult.errors)
|
|
281
281
|
})
|
|
282
282
|
|
|
283
|
+
it('should not the form on blur if configured but the form was never validated yet', async () => {
|
|
284
|
+
const schema = z.object({
|
|
285
|
+
name: z.string().min(2),
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const initialData = { name: 'A' }
|
|
289
|
+
const form = useForm({
|
|
290
|
+
initialData,
|
|
291
|
+
schema,
|
|
292
|
+
validationStrategy: 'onTouch',
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const nameField = form.getField('name')
|
|
296
|
+
|
|
297
|
+
// Simulate blur event
|
|
298
|
+
nameField.onBlur()
|
|
299
|
+
|
|
300
|
+
// onBlur is not async but the validation runs async
|
|
301
|
+
await delay()
|
|
302
|
+
|
|
303
|
+
expect(form.isValid.value).toBe(true)
|
|
304
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(0)
|
|
305
|
+
})
|
|
306
|
+
|
|
283
307
|
it('should validate the form on blur if configured', async () => {
|
|
284
308
|
const schema = z.object({
|
|
285
309
|
name: z.string().min(2),
|
|
@@ -294,6 +318,8 @@ describe('useValidation', () => {
|
|
|
294
318
|
|
|
295
319
|
const nameField = form.getField('name')
|
|
296
320
|
|
|
321
|
+
await form.validateForm()
|
|
322
|
+
|
|
297
323
|
// Simulate blur event
|
|
298
324
|
nameField.onBlur()
|
|
299
325
|
|
|
@@ -324,6 +350,17 @@ describe('useValidation', () => {
|
|
|
324
350
|
const nameField = form.getField('name')
|
|
325
351
|
form.getField('email')
|
|
326
352
|
|
|
353
|
+
await form.validateForm()
|
|
354
|
+
|
|
355
|
+
nameField.data.value = 'ab'
|
|
356
|
+
|
|
357
|
+
await delay()
|
|
358
|
+
|
|
359
|
+
// Data changed, but validation not triggered yet
|
|
360
|
+
expect(form.isValid.value).toBe(false)
|
|
361
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(1)
|
|
362
|
+
expect(form.errors.value.propertyErrors.email ?? []).toHaveLength(1)
|
|
363
|
+
|
|
327
364
|
// Simulate blur event
|
|
328
365
|
nameField.onBlur()
|
|
329
366
|
|
|
@@ -331,8 +368,8 @@ describe('useValidation', () => {
|
|
|
331
368
|
await delay()
|
|
332
369
|
|
|
333
370
|
expect(form.isValid.value).toBe(false)
|
|
334
|
-
expect(form.errors.value.propertyErrors.name).toHaveLength(
|
|
335
|
-
expect(form.errors.value.propertyErrors.email ?? []).toHaveLength(
|
|
371
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(0)
|
|
372
|
+
expect(form.errors.value.propertyErrors.email ?? []).toHaveLength(1)
|
|
336
373
|
})
|
|
337
374
|
|
|
338
375
|
it('should validate the form on form open if configured', async () => {
|
|
@@ -379,4 +416,63 @@ describe('useValidation', () => {
|
|
|
379
416
|
expect(form.isValidated.value).toBe(false)
|
|
380
417
|
expect(form.isValid.value).toBe(true)
|
|
381
418
|
})
|
|
419
|
+
|
|
420
|
+
it('should validate the form on data change if configured', async () => {
|
|
421
|
+
const schema = z.object({
|
|
422
|
+
name: z.string().min(2),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
const initialData = { name: 'ABC' }
|
|
426
|
+
const form = useForm({
|
|
427
|
+
initialData,
|
|
428
|
+
schema,
|
|
429
|
+
validationStrategy: 'onDataChange',
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const nameField = form.getField('name')
|
|
433
|
+
|
|
434
|
+
await form.validateForm()
|
|
435
|
+
|
|
436
|
+
expect(form.isValid.value).toBe(true)
|
|
437
|
+
|
|
438
|
+
nameField.data.value = 'a'
|
|
439
|
+
|
|
440
|
+
await delay()
|
|
441
|
+
|
|
442
|
+
expect(form.isValid.value).toBe(false)
|
|
443
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(1)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('should not validate other fields on data change', async () => {
|
|
447
|
+
const schema = z.object({
|
|
448
|
+
name: z.string().min(2),
|
|
449
|
+
foo: z.string().min(2),
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
const initialData = {
|
|
453
|
+
name: 'b',
|
|
454
|
+
foo: 'c',
|
|
455
|
+
}
|
|
456
|
+
const form = useForm({
|
|
457
|
+
initialData,
|
|
458
|
+
schema,
|
|
459
|
+
validationStrategy: 'onDataChange',
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const nameField = form.getField('name')
|
|
463
|
+
|
|
464
|
+
await form.validateForm()
|
|
465
|
+
|
|
466
|
+
expect(form.isValid.value).toBe(false)
|
|
467
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(1)
|
|
468
|
+
expect(form.errors.value.propertyErrors.foo).toHaveLength(1)
|
|
469
|
+
|
|
470
|
+
nameField.data.value = 'abc'
|
|
471
|
+
|
|
472
|
+
await delay()
|
|
473
|
+
|
|
474
|
+
expect(form.isValid.value).toBe(false)
|
|
475
|
+
expect(form.errors.value.propertyErrors.name).toHaveLength(0)
|
|
476
|
+
expect(form.errors.value.propertyErrors.foo).toHaveLength(1)
|
|
477
|
+
})
|
|
382
478
|
})
|