@teamnovu/kit-vue-forms 0.0.13 → 0.0.14
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 +2 -3
- package/dist/composables/useFieldRegistry.d.ts +2 -1
- package/dist/{index.mjs → index.js} +155 -151
- package/package.json +6 -4
- package/src/components/Field.vue +3 -1
- package/src/composables/useField.ts +3 -24
- package/src/composables/useFieldRegistry.ts +11 -1
- package/src/composables/useForm.ts +1 -1
- package/tests/useValidation.test.ts +25 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { MaybeRef, MaybeRefOrGetter } from 'vue';
|
|
1
|
+
import { MaybeRef, MaybeRefOrGetter, WritableComputedRef } from 'vue';
|
|
2
2
|
import { FormField } from '../types/form';
|
|
3
3
|
import { ValidationErrors } from '../types/validation';
|
|
4
4
|
export interface UseFieldOptions<T, K extends string> {
|
|
5
5
|
value?: MaybeRef<T>;
|
|
6
6
|
initialValue?: MaybeRefOrGetter<Readonly<T>>;
|
|
7
7
|
path: K;
|
|
8
|
-
errors?:
|
|
8
|
+
errors?: WritableComputedRef<ValidationErrors>;
|
|
9
9
|
}
|
|
10
|
-
export declare function getEmptyField(): FormField<unknown, string>;
|
|
11
10
|
export declare function useField<T, K extends string>(options: UseFieldOptions<T, K>): FormField<T, K>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FieldsTuple, FormDataDefault, FormField } from '../types/form';
|
|
2
2
|
import { Paths, PickProps } from '../types/util';
|
|
3
3
|
import { UseFieldOptions } from './useField';
|
|
4
|
+
import { ValidationState } from './useValidation';
|
|
4
5
|
type FieldRegistryCache<T> = Record<Paths<T>, FormField<any, string>>;
|
|
5
6
|
export type ResolvedFormField<T, K extends Paths<T>> = FormField<PickProps<T, K>, K>;
|
|
6
7
|
export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>, 'path'>;
|
|
@@ -8,7 +9,7 @@ interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T>
|
|
|
8
9
|
data: T;
|
|
9
10
|
initialData: TIn;
|
|
10
11
|
}
|
|
11
|
-
export declare function useFieldRegistry<T extends FormDataDefault>(formState: FormState<T>): {
|
|
12
|
+
export declare function useFieldRegistry<T extends FormDataDefault>(formState: FormState<T>, validationState: ValidationState<T>): {
|
|
12
13
|
fields: FieldRegistryCache<T>;
|
|
13
14
|
getField: <K extends Paths<T>>(path: K) => ResolvedFormField<T, K>;
|
|
14
15
|
getFields: <TData extends T>() => FieldsTuple<TData>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var x = Object.defineProperty;
|
|
2
2
|
var K = (e, r, t) => r in e ? x(e, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[r] = t;
|
|
3
|
-
var
|
|
4
|
-
import { toValue as W, toRaw as I, computed as
|
|
3
|
+
var D = (e, r, t) => K(e, typeof r != "symbol" ? r + "" : r, t);
|
|
4
|
+
import { toValue as W, toRaw as I, computed as u, unref as d, reactive as m, toRefs as O, toRef as S, ref as A, watch as E, isRef as _, getCurrentScope as J, onBeforeUnmount as T, defineComponent as B, renderSlot as G, normalizeProps as L, guardReactiveProps as U } from "vue";
|
|
5
5
|
import "zod";
|
|
6
6
|
import "@vueuse/core";
|
|
7
7
|
function y(e) {
|
|
@@ -21,22 +21,22 @@ function Z(e, r, t) {
|
|
|
21
21
|
const s = Array.isArray(r) ? r : N(r);
|
|
22
22
|
if (s.length === 0)
|
|
23
23
|
throw new Error("Path cannot be empty");
|
|
24
|
-
const a = s.at(-1),
|
|
25
|
-
(
|
|
24
|
+
const a = s.at(-1), o = s.slice(0, -1).reduce(
|
|
25
|
+
(v, c) => v[c],
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
27
|
e
|
|
28
28
|
);
|
|
29
|
-
|
|
29
|
+
o[a] = t;
|
|
30
30
|
}
|
|
31
|
-
const z = (e, r) =>
|
|
31
|
+
const z = (e, r) => u({
|
|
32
32
|
get() {
|
|
33
|
-
return w(
|
|
33
|
+
return w(d(e), d(r));
|
|
34
34
|
},
|
|
35
35
|
set(t) {
|
|
36
|
-
Z(
|
|
36
|
+
Z(d(e), d(r), t);
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
|
-
function
|
|
39
|
+
function F(e, r) {
|
|
40
40
|
return !e && !r ? "" : !e && r ? r : !r && e ? e : `${e}.${r}`;
|
|
41
41
|
}
|
|
42
42
|
function k(e, r) {
|
|
@@ -44,7 +44,7 @@ function k(e, r) {
|
|
|
44
44
|
return e;
|
|
45
45
|
const t = `${r}.`, s = Object.fromEntries(
|
|
46
46
|
Object.entries(e.propertyErrors).filter(([a]) => a.startsWith(t)).map(
|
|
47
|
-
([a,
|
|
47
|
+
([a, o]) => [a.slice(t.length), o]
|
|
48
48
|
)
|
|
49
49
|
);
|
|
50
50
|
return {
|
|
@@ -57,65 +57,69 @@ function q(e) {
|
|
|
57
57
|
const r = m({
|
|
58
58
|
value: e.value,
|
|
59
59
|
path: e.path,
|
|
60
|
-
initialValue:
|
|
61
|
-
errors:
|
|
60
|
+
initialValue: u(() => Object.freeze(y(e.initialValue))),
|
|
61
|
+
errors: e.errors,
|
|
62
62
|
touched: !1
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
r.errors = d || [];
|
|
66
|
-
});
|
|
67
|
-
const t = f(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (d) => {
|
|
68
|
-
r.value = d;
|
|
63
|
+
}), t = u(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (f) => {
|
|
64
|
+
r.value = f;
|
|
69
65
|
}, a = () => {
|
|
70
66
|
r.touched = !0;
|
|
71
|
-
}, n = () => {
|
|
72
67
|
}, o = () => {
|
|
68
|
+
}, v = () => {
|
|
73
69
|
r.value = y(r.initialValue), r.touched = !1, r.errors = [];
|
|
74
|
-
},
|
|
75
|
-
r.errors =
|
|
76
|
-
},
|
|
70
|
+
}, c = (f) => {
|
|
71
|
+
r.errors = f;
|
|
72
|
+
}, i = () => {
|
|
77
73
|
r.errors = [];
|
|
78
|
-
},
|
|
74
|
+
}, l = O(r);
|
|
79
75
|
return {
|
|
80
|
-
data:
|
|
81
|
-
path:
|
|
82
|
-
initialValue:
|
|
83
|
-
errors:
|
|
84
|
-
touched:
|
|
76
|
+
data: l.value,
|
|
77
|
+
path: l.path,
|
|
78
|
+
initialValue: l.initialValue,
|
|
79
|
+
errors: l.errors,
|
|
80
|
+
touched: l.touched,
|
|
85
81
|
dirty: t,
|
|
86
82
|
setData: s,
|
|
87
83
|
onBlur: a,
|
|
88
|
-
onFocus:
|
|
89
|
-
reset:
|
|
90
|
-
setErrors:
|
|
91
|
-
clearErrors:
|
|
84
|
+
onFocus: o,
|
|
85
|
+
reset: v,
|
|
86
|
+
setErrors: c,
|
|
87
|
+
clearErrors: i
|
|
92
88
|
};
|
|
93
89
|
}
|
|
94
|
-
function H(e) {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
},
|
|
99
|
-
if (!
|
|
100
|
-
const
|
|
101
|
-
path:
|
|
102
|
-
value: z(
|
|
103
|
-
initialValue:
|
|
90
|
+
function H(e, r) {
|
|
91
|
+
const t = {}, s = (c) => {
|
|
92
|
+
const i = d(c.path);
|
|
93
|
+
t[i] = c;
|
|
94
|
+
}, a = (c) => {
|
|
95
|
+
if (!t[c]) {
|
|
96
|
+
const i = q({
|
|
97
|
+
path: c,
|
|
98
|
+
value: z(S(e, "data"), c),
|
|
99
|
+
initialValue: u(() => w(e.initialData, c)),
|
|
100
|
+
errors: u({
|
|
101
|
+
get() {
|
|
102
|
+
return r.errors.value.propertyErrors[c] || [];
|
|
103
|
+
},
|
|
104
|
+
set(l) {
|
|
105
|
+
r.errors.value.propertyErrors[c] = l;
|
|
106
|
+
}
|
|
107
|
+
})
|
|
104
108
|
});
|
|
105
|
-
return
|
|
109
|
+
return s(i), i;
|
|
106
110
|
}
|
|
107
|
-
return
|
|
111
|
+
return t[c];
|
|
108
112
|
};
|
|
109
113
|
return {
|
|
110
|
-
fields:
|
|
111
|
-
getField:
|
|
112
|
-
getFields: () => Object.values(
|
|
113
|
-
registerField:
|
|
114
|
-
defineField: (
|
|
114
|
+
fields: t,
|
|
115
|
+
getField: a,
|
|
116
|
+
getFields: () => Object.values(t),
|
|
117
|
+
registerField: s,
|
|
118
|
+
defineField: (c) => a(c.path)
|
|
115
119
|
};
|
|
116
120
|
}
|
|
117
121
|
function Q(e) {
|
|
118
|
-
const r =
|
|
122
|
+
const r = u(() => e.getFields().some((s) => d(s.dirty))), t = u(() => e.getFields().some((s) => d(s.touched)));
|
|
119
123
|
return {
|
|
120
124
|
isDirty: r,
|
|
121
125
|
isTouched: t
|
|
@@ -141,7 +145,7 @@ function C(...e) {
|
|
|
141
145
|
}
|
|
142
146
|
function Y(...e) {
|
|
143
147
|
return e.map((t) => Object.keys(t)).flat().reduce((t, s) => {
|
|
144
|
-
const a = e.map((
|
|
148
|
+
const a = e.map((o) => o[s]).filter(Boolean);
|
|
145
149
|
return {
|
|
146
150
|
...t,
|
|
147
151
|
[s]: C(...a)
|
|
@@ -170,10 +174,10 @@ function j(e) {
|
|
|
170
174
|
}
|
|
171
175
|
function rr(e) {
|
|
172
176
|
const r = e.issues.filter((s) => s.path.length === 0).map((s) => s.message), t = e.issues.filter((s) => s.path.length > 0).reduce((s, a) => {
|
|
173
|
-
const
|
|
177
|
+
const o = a.path.join(".");
|
|
174
178
|
return {
|
|
175
179
|
...s,
|
|
176
|
-
[
|
|
180
|
+
[o]: [...s[o] ?? [], a.message]
|
|
177
181
|
};
|
|
178
182
|
}, {});
|
|
179
183
|
return {
|
|
@@ -181,7 +185,7 @@ function rr(e) {
|
|
|
181
185
|
propertyErrors: t
|
|
182
186
|
};
|
|
183
187
|
}
|
|
184
|
-
const
|
|
188
|
+
const V = {
|
|
185
189
|
isValid: !0,
|
|
186
190
|
errors: {
|
|
187
191
|
general: [],
|
|
@@ -194,10 +198,10 @@ class er {
|
|
|
194
198
|
}
|
|
195
199
|
async validate(r) {
|
|
196
200
|
if (!this.schema)
|
|
197
|
-
return
|
|
201
|
+
return V;
|
|
198
202
|
const t = await this.schema.safeParseAsync(r);
|
|
199
203
|
if (t.success)
|
|
200
|
-
return
|
|
204
|
+
return V;
|
|
201
205
|
const s = rr(t.error);
|
|
202
206
|
return {
|
|
203
207
|
isValid: !1,
|
|
@@ -214,10 +218,10 @@ class tr {
|
|
|
214
218
|
}
|
|
215
219
|
async validate(r) {
|
|
216
220
|
if (!this.validateFn)
|
|
217
|
-
return
|
|
221
|
+
return V;
|
|
218
222
|
try {
|
|
219
223
|
const t = await this.validateFn(r);
|
|
220
|
-
return t.isValid ?
|
|
224
|
+
return t.isValid ? V : t;
|
|
221
225
|
} catch (t) {
|
|
222
226
|
return {
|
|
223
227
|
isValid: !1,
|
|
@@ -231,8 +235,8 @@ class tr {
|
|
|
231
235
|
}
|
|
232
236
|
class sr {
|
|
233
237
|
constructor(r, t) {
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
D(this, "schemaValidator");
|
|
239
|
+
D(this, "functionValidator");
|
|
236
240
|
this.schema = r, this.validateFn = t, this.schemaValidator = new er(this.schema), this.functionValidator = new tr(this.validateFn);
|
|
237
241
|
}
|
|
238
242
|
async validate(r) {
|
|
@@ -247,70 +251,70 @@ class sr {
|
|
|
247
251
|
}
|
|
248
252
|
}
|
|
249
253
|
function b(e) {
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
return u(() => new sr(
|
|
255
|
+
d(e.schema),
|
|
256
|
+
d(e.validateFn)
|
|
253
257
|
));
|
|
254
258
|
}
|
|
255
259
|
function ar(e, r) {
|
|
256
260
|
const t = m({
|
|
257
261
|
validators: A([b(r)]),
|
|
258
262
|
isValidated: !1,
|
|
259
|
-
errors:
|
|
263
|
+
errors: d(r.errors) ?? V.errors
|
|
260
264
|
});
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
}, { immediate: !0 }),
|
|
265
|
+
E(() => d(r.errors), async () => {
|
|
266
|
+
const i = await a();
|
|
267
|
+
o(i.errors);
|
|
268
|
+
}, { immediate: !0 }), E(
|
|
265
269
|
[() => t.validators],
|
|
266
|
-
async (
|
|
270
|
+
async (i) => {
|
|
267
271
|
if (t.isValidated)
|
|
268
|
-
if (
|
|
269
|
-
const
|
|
270
|
-
t.errors =
|
|
272
|
+
if (i) {
|
|
273
|
+
const l = await a();
|
|
274
|
+
t.errors = l.errors;
|
|
271
275
|
} else
|
|
272
|
-
t.errors =
|
|
276
|
+
t.errors = V.errors;
|
|
273
277
|
},
|
|
274
278
|
{ immediate: !0 }
|
|
275
|
-
),
|
|
276
|
-
t.isValidated &&
|
|
279
|
+
), E(() => e.data, () => {
|
|
280
|
+
t.isValidated && v();
|
|
277
281
|
});
|
|
278
|
-
const s = (
|
|
279
|
-
const
|
|
280
|
-
return t.validators.push(
|
|
282
|
+
const s = (i) => {
|
|
283
|
+
const l = _(i) ? i : b(i);
|
|
284
|
+
return t.validators.push(l), J() && T(() => {
|
|
281
285
|
t.validators = t.validators.filter(
|
|
282
|
-
(
|
|
286
|
+
(f) => f !== l
|
|
283
287
|
);
|
|
284
|
-
}),
|
|
288
|
+
}), l;
|
|
285
289
|
};
|
|
286
290
|
async function a() {
|
|
287
|
-
const
|
|
288
|
-
t.validators.filter((
|
|
289
|
-
),
|
|
290
|
-
let { errors:
|
|
291
|
-
if (!
|
|
292
|
-
const
|
|
293
|
-
|
|
291
|
+
const i = await Promise.all(
|
|
292
|
+
t.validators.filter((p) => d(p) !== void 0).map((p) => d(p).validate(e.data))
|
|
293
|
+
), l = i.every((p) => p.isValid);
|
|
294
|
+
let { errors: f } = V;
|
|
295
|
+
if (!l) {
|
|
296
|
+
const p = i.map((R) => R.errors);
|
|
297
|
+
f = P(...p);
|
|
294
298
|
}
|
|
295
299
|
return {
|
|
296
|
-
errors:
|
|
297
|
-
isValid:
|
|
300
|
+
errors: f,
|
|
301
|
+
isValid: l
|
|
298
302
|
};
|
|
299
303
|
}
|
|
300
|
-
const
|
|
301
|
-
t.errors = P(
|
|
302
|
-
},
|
|
303
|
-
const
|
|
304
|
-
return
|
|
305
|
-
isValid: !j(
|
|
304
|
+
const o = (i) => {
|
|
305
|
+
t.errors = P(d(r.errors) ?? V.errors, i);
|
|
306
|
+
}, v = async () => {
|
|
307
|
+
const i = await a();
|
|
308
|
+
return o(i.errors), t.isValidated = !0, {
|
|
309
|
+
isValid: !j(i.errors),
|
|
306
310
|
errors: t.errors
|
|
307
311
|
};
|
|
308
|
-
},
|
|
312
|
+
}, c = u(() => !j(t.errors));
|
|
309
313
|
return {
|
|
310
314
|
...O(t),
|
|
311
|
-
validateForm:
|
|
315
|
+
validateForm: v,
|
|
312
316
|
defineValidator: s,
|
|
313
|
-
isValid:
|
|
317
|
+
isValid: c
|
|
314
318
|
};
|
|
315
319
|
}
|
|
316
320
|
class ir {
|
|
@@ -320,16 +324,16 @@ class ir {
|
|
|
320
324
|
async validate(r) {
|
|
321
325
|
const t = w(r, this.path);
|
|
322
326
|
if (!this.validator)
|
|
323
|
-
return
|
|
327
|
+
return V;
|
|
324
328
|
const s = await this.validator.validate(t);
|
|
325
329
|
return {
|
|
326
330
|
isValid: s.isValid,
|
|
327
331
|
errors: {
|
|
328
332
|
general: s.errors.general || [],
|
|
329
333
|
propertyErrors: s.errors.propertyErrors ? Object.fromEntries(
|
|
330
|
-
Object.entries(s.errors.propertyErrors).map(([a,
|
|
331
|
-
|
|
332
|
-
|
|
334
|
+
Object.entries(s.errors.propertyErrors).map(([a, o]) => [
|
|
335
|
+
F(this.path, a),
|
|
336
|
+
o
|
|
333
337
|
])
|
|
334
338
|
) : {}
|
|
335
339
|
}
|
|
@@ -337,49 +341,49 @@ class ir {
|
|
|
337
341
|
}
|
|
338
342
|
}
|
|
339
343
|
function nr(e, r, t) {
|
|
340
|
-
const s = z(e.data, r), a =
|
|
341
|
-
...
|
|
342
|
-
path:
|
|
344
|
+
const s = z(e.data, r), a = u(() => w(e.initialData.value, r)), o = (n) => ({
|
|
345
|
+
...n,
|
|
346
|
+
path: u(() => d(n.path).replace(r + ".", "")),
|
|
343
347
|
setData: (h) => {
|
|
344
|
-
|
|
348
|
+
n.setData(h);
|
|
345
349
|
}
|
|
346
|
-
}),
|
|
347
|
-
const h =
|
|
348
|
-
return g ?
|
|
349
|
-
},
|
|
350
|
-
const h =
|
|
351
|
-
...
|
|
350
|
+
}), v = (n) => {
|
|
351
|
+
const h = F(r, n), g = e.getField(h);
|
|
352
|
+
return g ? o(g) : {};
|
|
353
|
+
}, c = (n) => {
|
|
354
|
+
const h = F(r, n.path), g = e.defineField({
|
|
355
|
+
...n,
|
|
352
356
|
path: h
|
|
353
357
|
});
|
|
354
|
-
return
|
|
355
|
-
},
|
|
356
|
-
const h =
|
|
358
|
+
return o(g);
|
|
359
|
+
}, i = () => e.getFields().filter((n) => {
|
|
360
|
+
const h = n.path.value;
|
|
357
361
|
return h.startsWith(r + ".") || h === r;
|
|
358
|
-
}).map((
|
|
359
|
-
const h =
|
|
362
|
+
}).map((n) => o(n)), l = () => e.getFields().filter((n) => {
|
|
363
|
+
const h = n.path.value;
|
|
360
364
|
return h.startsWith(r + ".") || h === r;
|
|
361
|
-
}),
|
|
365
|
+
}), f = u(() => l().some((n) => n.dirty.value)), p = u(() => l().some((n) => n.touched.value)), R = u(() => e.isValid.value), M = u(() => e.isValidated.value), $ = u(() => k(d(e.errors), r));
|
|
362
366
|
return {
|
|
363
367
|
data: s,
|
|
364
368
|
initialData: a,
|
|
365
|
-
defineField:
|
|
366
|
-
getField:
|
|
367
|
-
getFields:
|
|
368
|
-
isDirty:
|
|
369
|
-
isTouched:
|
|
369
|
+
defineField: c,
|
|
370
|
+
getField: v,
|
|
371
|
+
getFields: i,
|
|
372
|
+
isDirty: f,
|
|
373
|
+
isTouched: p,
|
|
370
374
|
isValid: R,
|
|
371
375
|
isValidated: M,
|
|
372
376
|
errors: $,
|
|
373
|
-
defineValidator: (
|
|
374
|
-
const h = _(
|
|
375
|
-
() => new ir(r,
|
|
377
|
+
defineValidator: (n) => {
|
|
378
|
+
const h = _(n) ? n : b(n), g = u(
|
|
379
|
+
() => new ir(r, d(h))
|
|
376
380
|
);
|
|
377
381
|
return e.defineValidator(g), h;
|
|
378
382
|
},
|
|
379
|
-
reset: () =>
|
|
383
|
+
reset: () => l().forEach((n) => n.reset()),
|
|
380
384
|
validateForm: () => e.validateForm(),
|
|
381
|
-
getSubForm: (
|
|
382
|
-
const g =
|
|
385
|
+
getSubForm: (n, h) => {
|
|
386
|
+
const g = F(r, n);
|
|
383
387
|
return e.getSubForm(
|
|
384
388
|
g,
|
|
385
389
|
h
|
|
@@ -387,34 +391,34 @@ function nr(e, r, t) {
|
|
|
387
391
|
}
|
|
388
392
|
};
|
|
389
393
|
}
|
|
390
|
-
function
|
|
391
|
-
const r =
|
|
394
|
+
function pr(e) {
|
|
395
|
+
const r = u(() => Object.freeze(y(e.initialData))), t = A(y(r)), s = m({
|
|
392
396
|
initialData: r,
|
|
393
397
|
data: t
|
|
394
398
|
});
|
|
395
|
-
|
|
396
|
-
s.data = y(
|
|
399
|
+
E(r, (f) => {
|
|
400
|
+
s.data = y(f);
|
|
397
401
|
});
|
|
398
|
-
const a =
|
|
399
|
-
t.value = y(r),
|
|
400
|
-
(
|
|
402
|
+
const a = ar(s, e), o = H(s, a), v = Q(o), c = () => {
|
|
403
|
+
t.value = y(r), o.getFields().forEach(
|
|
404
|
+
(f) => f.reset()
|
|
401
405
|
);
|
|
402
406
|
};
|
|
403
|
-
function
|
|
404
|
-
return nr(
|
|
407
|
+
function i(f, p) {
|
|
408
|
+
return nr(l, f);
|
|
405
409
|
}
|
|
406
|
-
const
|
|
407
|
-
...a,
|
|
408
|
-
...n,
|
|
410
|
+
const l = {
|
|
409
411
|
...o,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
412
|
+
...a,
|
|
413
|
+
...v,
|
|
414
|
+
reset: c,
|
|
415
|
+
getSubForm: i,
|
|
416
|
+
initialData: S(s, "initialData"),
|
|
417
|
+
data: S(s, "data")
|
|
414
418
|
};
|
|
415
|
-
return
|
|
419
|
+
return l;
|
|
416
420
|
}
|
|
417
|
-
const
|
|
421
|
+
const Vr = /* @__PURE__ */ B({
|
|
418
422
|
__name: "Field",
|
|
419
423
|
props: {
|
|
420
424
|
form: {},
|
|
@@ -426,11 +430,11 @@ const pr = /* @__PURE__ */ B({
|
|
|
426
430
|
setup(e) {
|
|
427
431
|
const r = e, t = r.form.defineField({
|
|
428
432
|
path: r.path
|
|
429
|
-
});
|
|
430
|
-
return (
|
|
433
|
+
}), s = m(t);
|
|
434
|
+
return (a, o) => G(a.$slots, "default", L(U(s)));
|
|
431
435
|
}
|
|
432
436
|
});
|
|
433
437
|
export {
|
|
434
|
-
|
|
435
|
-
|
|
438
|
+
Vr as Field,
|
|
439
|
+
pr as useForm
|
|
436
440
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamnovu/kit-vue-forms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
@@ -21,10 +22,11 @@
|
|
|
21
22
|
"vue": "^3.0.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"vue": "^
|
|
25
|
-
"vitest": "^2.0.0",
|
|
25
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
26
26
|
"@vitest/ui": "^2.0.0",
|
|
27
|
-
"happy-dom": "^12.0.0"
|
|
27
|
+
"happy-dom": "^12.0.0",
|
|
28
|
+
"vitest": "^2.0.0",
|
|
29
|
+
"vue": "^3.5.13"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"@vueuse/core": "^13.5.0",
|
package/src/components/Field.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<slot v-bind="
|
|
2
|
+
<slot v-bind="slotData" />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script
|
|
@@ -20,4 +20,6 @@ const props = defineProps<FieldProps<TData, TPath>>()
|
|
|
20
20
|
const field = props.form.defineField({
|
|
21
21
|
path: props.path,
|
|
22
22
|
})
|
|
23
|
+
|
|
24
|
+
const slotData = reactive(field)
|
|
23
25
|
</script>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, reactive,
|
|
1
|
+
import { computed, reactive, toRefs, watch, type MaybeRef, type MaybeRefOrGetter, type WritableComputedRef } from 'vue'
|
|
2
2
|
import type { FormField } from '../types/form'
|
|
3
3
|
import type { ValidationErrorMessage, ValidationErrors } from '../types/validation'
|
|
4
4
|
import { cloneRefValue } from '../utils/general'
|
|
@@ -7,24 +7,7 @@ export interface UseFieldOptions<T, K extends string> {
|
|
|
7
7
|
value?: MaybeRef<T>
|
|
8
8
|
initialValue?: MaybeRefOrGetter<Readonly<T>>
|
|
9
9
|
path: K
|
|
10
|
-
errors?:
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getEmptyField(): FormField<unknown, string> {
|
|
14
|
-
return {
|
|
15
|
-
setData: () => {},
|
|
16
|
-
onBlur: () => {},
|
|
17
|
-
onFocus: () => {},
|
|
18
|
-
reset: () => {},
|
|
19
|
-
setErrors: () => {},
|
|
20
|
-
clearErrors: () => {},
|
|
21
|
-
data: ref(undefined),
|
|
22
|
-
initialValue: ref(undefined),
|
|
23
|
-
path: ref(''),
|
|
24
|
-
errors: ref([]),
|
|
25
|
-
touched: ref(false),
|
|
26
|
-
dirty: computed(() => false),
|
|
27
|
-
}
|
|
10
|
+
errors?: WritableComputedRef<ValidationErrors>
|
|
28
11
|
}
|
|
29
12
|
|
|
30
13
|
export function useField<T, K extends string>(options: UseFieldOptions<T, K>): FormField<T, K> {
|
|
@@ -32,14 +15,10 @@ export function useField<T, K extends string>(options: UseFieldOptions<T, K>): F
|
|
|
32
15
|
value: options.value,
|
|
33
16
|
path: options.path,
|
|
34
17
|
initialValue: computed(() => Object.freeze(cloneRefValue(options.initialValue))),
|
|
35
|
-
errors:
|
|
18
|
+
errors: options.errors,
|
|
36
19
|
touched: false,
|
|
37
20
|
})
|
|
38
21
|
|
|
39
|
-
watch(() => unref(options.errors), (newValue) => {
|
|
40
|
-
state.errors = newValue || []
|
|
41
|
-
})
|
|
42
|
-
|
|
43
22
|
const dirty = computed(() => {
|
|
44
23
|
return JSON.stringify(state.value) !== JSON.stringify(state.initialValue)
|
|
45
24
|
})
|
|
@@ -2,7 +2,8 @@ import { computed, toRef, unref } from 'vue'
|
|
|
2
2
|
import type { FieldsTuple, FormDataDefault, FormField } from '../types/form'
|
|
3
3
|
import type { Paths, PickProps } from '../types/util'
|
|
4
4
|
import { getLens, getNestedValue } from '../utils/path'
|
|
5
|
-
import {
|
|
5
|
+
import { useField, type UseFieldOptions } from './useField'
|
|
6
|
+
import type { ValidationState } from './useValidation'
|
|
6
7
|
|
|
7
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
9
|
type FieldRegistryCache<T> = Record<Paths<T>, FormField<any, string>>
|
|
@@ -18,6 +19,7 @@ interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T>
|
|
|
18
19
|
|
|
19
20
|
export function useFieldRegistry<T extends FormDataDefault>(
|
|
20
21
|
formState: FormState<T>,
|
|
22
|
+
validationState: ValidationState<T>,
|
|
21
23
|
) {
|
|
22
24
|
const fields = {} as FieldRegistryCache<T>
|
|
23
25
|
|
|
@@ -32,6 +34,14 @@ export function useFieldRegistry<T extends FormDataDefault>(
|
|
|
32
34
|
path,
|
|
33
35
|
value: getLens(toRef(formState, 'data'), path),
|
|
34
36
|
initialValue: computed(() => getNestedValue(formState.initialData, path)),
|
|
37
|
+
errors: computed({
|
|
38
|
+
get() {
|
|
39
|
+
return validationState.errors.value.propertyErrors[path] || []
|
|
40
|
+
},
|
|
41
|
+
set(newErrors) {
|
|
42
|
+
validationState.errors.value.propertyErrors[path] = newErrors
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
35
45
|
})
|
|
36
46
|
|
|
37
47
|
registerField(field)
|
|
@@ -31,8 +31,8 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
|
|
|
31
31
|
state.data = cloneRefValue(newValue)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
const fields = useFieldRegistry(state)
|
|
35
34
|
const validationState = useValidation(state, options)
|
|
35
|
+
const fields = useFieldRegistry(state, validationState)
|
|
36
36
|
const formState = useFormState(fields)
|
|
37
37
|
|
|
38
38
|
const reset = () => {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { nextTick, ref } from 'vue'
|
|
3
3
|
import { z } from 'zod'
|
|
4
|
+
import { useForm } from '../src/composables/useForm'
|
|
4
5
|
import { SuccessValidationResult, useValidation } from '../src/composables/useValidation'
|
|
5
6
|
import { ErrorBag } from '../src/types/validation'
|
|
6
7
|
import { hasErrors } from '../src/utils/validation'
|
|
7
8
|
|
|
9
|
+
const delay = (ms: number = 0) => new Promise(resolve => setTimeout(resolve, ms))
|
|
10
|
+
|
|
8
11
|
describe('useValidation', () => {
|
|
9
12
|
it('should initialize with no errors', () => {
|
|
10
13
|
const formState = { data: { name: 'John' } }
|
|
@@ -213,4 +216,26 @@ describe('useValidation', () => {
|
|
|
213
216
|
expect(result.errors.propertyErrors.name).toEqual(['Too small: expected string to have >=2 characters']) // From schema validation
|
|
214
217
|
expect(result.errors.propertyErrors.email).toEqual(['External email error'])
|
|
215
218
|
})
|
|
219
|
+
|
|
220
|
+
it('should pass errors to the field', async () => {
|
|
221
|
+
const errors = ref<ErrorBag>(SuccessValidationResult.errors)
|
|
222
|
+
|
|
223
|
+
const form = useForm({
|
|
224
|
+
initialData: { name: 'A' },
|
|
225
|
+
errors,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const field = form.getField('name')
|
|
229
|
+
|
|
230
|
+
expect(field.errors.value).toEqual([]) // Initially no errors
|
|
231
|
+
|
|
232
|
+
errors.value = {
|
|
233
|
+
general: [],
|
|
234
|
+
propertyErrors: { name: ['Name error'] },
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await delay()
|
|
238
|
+
|
|
239
|
+
expect(field.errors.value).toEqual(['Name error'])
|
|
240
|
+
})
|
|
216
241
|
})
|