@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.
@@ -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?: MaybeRef<ValidationErrors>;
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 S = (e, r, t) => K(e, typeof r != "symbol" ? r + "" : r, t);
4
- import { toValue as W, toRaw as I, computed as f, unref as u, reactive as m, watch as F, toRefs as O, toRef as D, ref as A, isRef as _, getCurrentScope as J, onBeforeUnmount as T, defineComponent as B, renderSlot as G, normalizeProps as L, guardReactiveProps as U } from "vue";
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), n = s.slice(0, -1).reduce(
25
- (o, v) => o[v],
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
- n[a] = t;
29
+ o[a] = t;
30
30
  }
31
- const z = (e, r) => f({
31
+ const z = (e, r) => u({
32
32
  get() {
33
- return w(u(e), u(r));
33
+ return w(d(e), d(r));
34
34
  },
35
35
  set(t) {
36
- Z(u(e), u(r), t);
36
+ Z(d(e), d(r), t);
37
37
  }
38
38
  });
39
- function E(e, r) {
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, n]) => [a.slice(t.length), n]
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: f(() => Object.freeze(y(e.initialValue))),
61
- errors: u(e.errors) || [],
60
+ initialValue: u(() => Object.freeze(y(e.initialValue))),
61
+ errors: e.errors,
62
62
  touched: !1
63
- });
64
- F(() => u(e.errors), (d) => {
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
- }, v = (d) => {
75
- r.errors = d;
76
- }, l = () => {
70
+ }, c = (f) => {
71
+ r.errors = f;
72
+ }, i = () => {
77
73
  r.errors = [];
78
- }, c = O(r);
74
+ }, l = O(r);
79
75
  return {
80
- data: c.value,
81
- path: c.path,
82
- initialValue: c.initialValue,
83
- errors: c.errors,
84
- touched: c.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: n,
89
- reset: o,
90
- setErrors: v,
91
- clearErrors: l
84
+ onFocus: o,
85
+ reset: v,
86
+ setErrors: c,
87
+ clearErrors: i
92
88
  };
93
89
  }
94
- function H(e) {
95
- const r = {}, t = (o) => {
96
- const v = u(o.path);
97
- r[v] = o;
98
- }, s = (o) => {
99
- if (!r[o]) {
100
- const v = q({
101
- path: o,
102
- value: z(D(e, "data"), o),
103
- initialValue: f(() => w(e.initialData, o))
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 t(v), v;
109
+ return s(i), i;
106
110
  }
107
- return r[o];
111
+ return t[c];
108
112
  };
109
113
  return {
110
- fields: r,
111
- getField: s,
112
- getFields: () => Object.values(r),
113
- registerField: t,
114
- defineField: (o) => s(o.path)
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 = f(() => e.getFields().some((s) => u(s.dirty))), t = f(() => e.getFields().some((s) => u(s.touched)));
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((n) => n[s]).filter(Boolean);
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 n = a.path.join(".");
177
+ const o = a.path.join(".");
174
178
  return {
175
179
  ...s,
176
- [n]: [...s[n] ?? [], a.message]
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 p = {
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 p;
201
+ return V;
198
202
  const t = await this.schema.safeParseAsync(r);
199
203
  if (t.success)
200
- return p;
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 p;
221
+ return V;
218
222
  try {
219
223
  const t = await this.validateFn(r);
220
- return t.isValid ? p : t;
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
- S(this, "schemaValidator");
235
- S(this, "functionValidator");
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 f(() => new sr(
251
- u(e.schema),
252
- u(e.validateFn)
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: u(r.errors) ?? p.errors
263
+ errors: d(r.errors) ?? V.errors
260
264
  });
261
- F(() => u(r.errors), async () => {
262
- const l = await a();
263
- n(l.errors);
264
- }, { immediate: !0 }), F(
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 (l) => {
270
+ async (i) => {
267
271
  if (t.isValidated)
268
- if (l) {
269
- const c = await a();
270
- t.errors = c.errors;
272
+ if (i) {
273
+ const l = await a();
274
+ t.errors = l.errors;
271
275
  } else
272
- t.errors = p.errors;
276
+ t.errors = V.errors;
273
277
  },
274
278
  { immediate: !0 }
275
- ), F(() => e.data, () => {
276
- t.isValidated && o();
279
+ ), E(() => e.data, () => {
280
+ t.isValidated && v();
277
281
  });
278
- const s = (l) => {
279
- const c = _(l) ? l : b(l);
280
- return t.validators.push(c), J() && T(() => {
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
- (d) => d !== c
286
+ (f) => f !== l
283
287
  );
284
- }), c;
288
+ }), l;
285
289
  };
286
290
  async function a() {
287
- const l = await Promise.all(
288
- t.validators.filter((V) => u(V) !== void 0).map((V) => u(V).validate(e.data))
289
- ), c = l.every((V) => V.isValid);
290
- let { errors: d } = p;
291
- if (!c) {
292
- const V = l.map((R) => R.errors);
293
- d = P(...V);
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: d,
297
- isValid: c
300
+ errors: f,
301
+ isValid: l
298
302
  };
299
303
  }
300
- const n = (l) => {
301
- t.errors = P(u(r.errors) ?? p.errors, l);
302
- }, o = async () => {
303
- const l = await a();
304
- return n(l.errors), t.isValidated = !0, {
305
- isValid: !j(l.errors),
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
- }, v = f(() => !j(t.errors));
312
+ }, c = u(() => !j(t.errors));
309
313
  return {
310
314
  ...O(t),
311
- validateForm: o,
315
+ validateForm: v,
312
316
  defineValidator: s,
313
- isValid: v
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 p;
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, n]) => [
331
- E(this.path, a),
332
- n
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 = f(() => w(e.initialData.value, r)), n = (i) => ({
341
- ...i,
342
- path: f(() => u(i.path).replace(r + ".", "")),
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
- i.setData(h);
348
+ n.setData(h);
345
349
  }
346
- }), o = (i) => {
347
- const h = E(r, i), g = e.getField(h);
348
- return g ? n(g) : {};
349
- }, v = (i) => {
350
- const h = E(r, i.path), g = e.defineField({
351
- ...i,
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 n(g);
355
- }, l = () => e.getFields().filter((i) => {
356
- const h = i.path.value;
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((i) => n(i)), c = () => e.getFields().filter((i) => {
359
- const h = i.path.value;
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
- }), d = f(() => c().some((i) => i.dirty.value)), V = f(() => c().some((i) => i.touched.value)), R = f(() => e.isValid.value), M = f(() => e.isValidated.value), $ = f(() => k(u(e.errors), r));
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: v,
366
- getField: o,
367
- getFields: l,
368
- isDirty: d,
369
- isTouched: V,
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: (i) => {
374
- const h = _(i) ? i : b(i), g = f(
375
- () => new ir(r, u(h))
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: () => c().forEach((i) => i.reset()),
383
+ reset: () => l().forEach((n) => n.reset()),
380
384
  validateForm: () => e.validateForm(),
381
- getSubForm: (i, h) => {
382
- const g = E(r, i);
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 Vr(e) {
391
- const r = f(() => Object.freeze(y(e.initialData))), t = A(y(r)), s = m({
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
- F(r, (d) => {
396
- s.data = y(d);
399
+ E(r, (f) => {
400
+ s.data = y(f);
397
401
  });
398
- const a = H(s), n = ar(s, e), o = Q(a), v = () => {
399
- t.value = y(r), a.getFields().forEach(
400
- (d) => d.reset()
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 l(d, V) {
404
- return nr(c, d);
407
+ function i(f, p) {
408
+ return nr(l, f);
405
409
  }
406
- const c = {
407
- ...a,
408
- ...n,
410
+ const l = {
409
411
  ...o,
410
- reset: v,
411
- getSubForm: l,
412
- initialData: D(s, "initialData"),
413
- data: D(s, "data")
412
+ ...a,
413
+ ...v,
414
+ reset: c,
415
+ getSubForm: i,
416
+ initialData: S(s, "initialData"),
417
+ data: S(s, "data")
414
418
  };
415
- return c;
419
+ return l;
416
420
  }
417
- const pr = /* @__PURE__ */ B({
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 (s, a) => G(s.$slots, "default", L(U(m(u(t)))));
433
+ }), s = m(t);
434
+ return (a, o) => G(a.$slots, "default", L(U(s)));
431
435
  }
432
436
  });
433
437
  export {
434
- pr as Field,
435
- Vr as useForm
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.13",
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": "^3.0.0",
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",
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <slot v-bind="reactive(field)" />
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, ref, toRefs, unref, watch, type MaybeRef, type MaybeRefOrGetter } from 'vue'
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?: MaybeRef<ValidationErrors>
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: unref(options.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 { getEmptyField, useField, type UseFieldOptions } from './useField'
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
  })