@teamnovu/kit-vue-forms 0.0.12 → 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,8 +1,9 @@
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 d, 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
+ import "@vueuse/core";
6
7
  function y(e) {
7
8
  const r = W(e), t = I(r);
8
9
  return structuredClone(t);
@@ -20,22 +21,22 @@ function Z(e, r, t) {
20
21
  const s = Array.isArray(r) ? r : N(r);
21
22
  if (s.length === 0)
22
23
  throw new Error("Path cannot be empty");
23
- const a = s.at(-1), n = s.slice(0, -1).reduce(
24
- (o, v) => o[v],
24
+ const a = s.at(-1), o = s.slice(0, -1).reduce(
25
+ (v, c) => v[c],
25
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
27
  e
27
28
  );
28
- n[a] = t;
29
+ o[a] = t;
29
30
  }
30
- const z = (e, r) => d({
31
+ const z = (e, r) => u({
31
32
  get() {
32
- return w(u(e), u(r));
33
+ return w(d(e), d(r));
33
34
  },
34
35
  set(t) {
35
- Z(u(e), u(r), t);
36
+ Z(d(e), d(r), t);
36
37
  }
37
38
  });
38
- function E(e, r) {
39
+ function F(e, r) {
39
40
  return !e && !r ? "" : !e && r ? r : !r && e ? e : `${e}.${r}`;
40
41
  }
41
42
  function k(e, r) {
@@ -43,7 +44,7 @@ function k(e, r) {
43
44
  return e;
44
45
  const t = `${r}.`, s = Object.fromEntries(
45
46
  Object.entries(e.propertyErrors).filter(([a]) => a.startsWith(t)).map(
46
- ([a, n]) => [a.slice(t.length), n]
47
+ ([a, o]) => [a.slice(t.length), o]
47
48
  )
48
49
  );
49
50
  return {
@@ -56,65 +57,69 @@ function q(e) {
56
57
  const r = m({
57
58
  value: e.value,
58
59
  path: e.path,
59
- initialValue: d(() => Object.freeze(y(e.initialValue))),
60
- errors: u(e.errors) || [],
60
+ initialValue: u(() => Object.freeze(y(e.initialValue))),
61
+ errors: e.errors,
61
62
  touched: !1
62
- });
63
- F(() => u(e.errors), (f) => {
64
- r.errors = f || [];
65
- });
66
- const t = d(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (f) => {
63
+ }), t = u(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (f) => {
67
64
  r.value = f;
68
65
  }, a = () => {
69
66
  r.touched = !0;
70
- }, n = () => {
71
67
  }, o = () => {
68
+ }, v = () => {
72
69
  r.value = y(r.initialValue), r.touched = !1, r.errors = [];
73
- }, v = (f) => {
70
+ }, c = (f) => {
74
71
  r.errors = f;
75
- }, l = () => {
72
+ }, i = () => {
76
73
  r.errors = [];
77
- }, c = O(r);
74
+ }, l = O(r);
78
75
  return {
79
- data: c.value,
80
- path: c.path,
81
- initialValue: c.initialValue,
82
- errors: c.errors,
83
- touched: c.touched,
76
+ data: l.value,
77
+ path: l.path,
78
+ initialValue: l.initialValue,
79
+ errors: l.errors,
80
+ touched: l.touched,
84
81
  dirty: t,
85
82
  setData: s,
86
83
  onBlur: a,
87
- onFocus: n,
88
- reset: o,
89
- setErrors: v,
90
- clearErrors: l
84
+ onFocus: o,
85
+ reset: v,
86
+ setErrors: c,
87
+ clearErrors: i
91
88
  };
92
89
  }
93
- function H(e) {
94
- const r = {}, t = (o) => {
95
- const v = u(o.path);
96
- r[v] = o;
97
- }, s = (o) => {
98
- if (!r[o]) {
99
- const v = q({
100
- path: o,
101
- value: z(D(e, "data"), o),
102
- initialValue: d(() => 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
+ })
103
108
  });
104
- return t(v), v;
109
+ return s(i), i;
105
110
  }
106
- return r[o];
111
+ return t[c];
107
112
  };
108
113
  return {
109
- fields: r,
110
- getField: s,
111
- getFields: () => Object.values(r),
112
- registerField: t,
113
- 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)
114
119
  };
115
120
  }
116
121
  function Q(e) {
117
- const r = d(() => e.getFields().some((s) => u(s.dirty))), t = d(() => 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)));
118
123
  return {
119
124
  isDirty: r,
120
125
  isTouched: t
@@ -140,7 +145,7 @@ function C(...e) {
140
145
  }
141
146
  function Y(...e) {
142
147
  return e.map((t) => Object.keys(t)).flat().reduce((t, s) => {
143
- const a = e.map((n) => n[s]).filter(Boolean);
148
+ const a = e.map((o) => o[s]).filter(Boolean);
144
149
  return {
145
150
  ...t,
146
151
  [s]: C(...a)
@@ -169,10 +174,10 @@ function j(e) {
169
174
  }
170
175
  function rr(e) {
171
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) => {
172
- const n = a.path.join(".");
177
+ const o = a.path.join(".");
173
178
  return {
174
179
  ...s,
175
- [n]: [...s[n] ?? [], a.message]
180
+ [o]: [...s[o] ?? [], a.message]
176
181
  };
177
182
  }, {});
178
183
  return {
@@ -180,7 +185,7 @@ function rr(e) {
180
185
  propertyErrors: t
181
186
  };
182
187
  }
183
- const p = {
188
+ const V = {
184
189
  isValid: !0,
185
190
  errors: {
186
191
  general: [],
@@ -193,10 +198,10 @@ class er {
193
198
  }
194
199
  async validate(r) {
195
200
  if (!this.schema)
196
- return p;
201
+ return V;
197
202
  const t = await this.schema.safeParseAsync(r);
198
203
  if (t.success)
199
- return p;
204
+ return V;
200
205
  const s = rr(t.error);
201
206
  return {
202
207
  isValid: !1,
@@ -213,10 +218,10 @@ class tr {
213
218
  }
214
219
  async validate(r) {
215
220
  if (!this.validateFn)
216
- return p;
221
+ return V;
217
222
  try {
218
223
  const t = await this.validateFn(r);
219
- return t.isValid ? p : t;
224
+ return t.isValid ? V : t;
220
225
  } catch (t) {
221
226
  return {
222
227
  isValid: !1,
@@ -230,8 +235,8 @@ class tr {
230
235
  }
231
236
  class sr {
232
237
  constructor(r, t) {
233
- S(this, "schemaValidator");
234
- S(this, "functionValidator");
238
+ D(this, "schemaValidator");
239
+ D(this, "functionValidator");
235
240
  this.schema = r, this.validateFn = t, this.schemaValidator = new er(this.schema), this.functionValidator = new tr(this.validateFn);
236
241
  }
237
242
  async validate(r) {
@@ -246,70 +251,70 @@ class sr {
246
251
  }
247
252
  }
248
253
  function b(e) {
249
- return d(() => new sr(
250
- u(e.schema),
251
- u(e.validateFn)
254
+ return u(() => new sr(
255
+ d(e.schema),
256
+ d(e.validateFn)
252
257
  ));
253
258
  }
254
259
  function ar(e, r) {
255
260
  const t = m({
256
261
  validators: A([b(r)]),
257
262
  isValidated: !1,
258
- errors: u(r.errors) ?? p.errors
263
+ errors: d(r.errors) ?? V.errors
259
264
  });
260
- F(() => u(r.errors), async () => {
261
- const l = await a();
262
- n(l.errors);
263
- }, { immediate: !0 }), F(
265
+ E(() => d(r.errors), async () => {
266
+ const i = await a();
267
+ o(i.errors);
268
+ }, { immediate: !0 }), E(
264
269
  [() => t.validators],
265
- async (l) => {
270
+ async (i) => {
266
271
  if (t.isValidated)
267
- if (l) {
268
- const c = await a();
269
- t.errors = c.errors;
272
+ if (i) {
273
+ const l = await a();
274
+ t.errors = l.errors;
270
275
  } else
271
- t.errors = p.errors;
276
+ t.errors = V.errors;
272
277
  },
273
278
  { immediate: !0 }
274
- ), F(() => e.data, () => {
275
- t.isValidated && o();
279
+ ), E(() => e.data, () => {
280
+ t.isValidated && v();
276
281
  });
277
- const s = (l) => {
278
- const c = _(l) ? l : b(l);
279
- 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(() => {
280
285
  t.validators = t.validators.filter(
281
- (f) => f !== c
286
+ (f) => f !== l
282
287
  );
283
- }), c;
288
+ }), l;
284
289
  };
285
290
  async function a() {
286
- const l = await Promise.all(
287
- t.validators.filter((V) => u(V) !== void 0).map((V) => u(V).validate(e.data))
288
- ), c = l.every((V) => V.isValid);
289
- let { errors: f } = p;
290
- if (!c) {
291
- const V = l.map((R) => R.errors);
292
- f = 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);
293
298
  }
294
299
  return {
295
300
  errors: f,
296
- isValid: c
301
+ isValid: l
297
302
  };
298
303
  }
299
- const n = (l) => {
300
- t.errors = P(u(r.errors) ?? p.errors, l);
301
- }, o = async () => {
302
- const l = await a();
303
- return n(l.errors), t.isValidated = !0, {
304
- 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),
305
310
  errors: t.errors
306
311
  };
307
- }, v = d(() => !j(t.errors));
312
+ }, c = u(() => !j(t.errors));
308
313
  return {
309
314
  ...O(t),
310
- validateForm: o,
315
+ validateForm: v,
311
316
  defineValidator: s,
312
- isValid: v
317
+ isValid: c
313
318
  };
314
319
  }
315
320
  class ir {
@@ -319,16 +324,16 @@ class ir {
319
324
  async validate(r) {
320
325
  const t = w(r, this.path);
321
326
  if (!this.validator)
322
- return p;
327
+ return V;
323
328
  const s = await this.validator.validate(t);
324
329
  return {
325
330
  isValid: s.isValid,
326
331
  errors: {
327
332
  general: s.errors.general || [],
328
333
  propertyErrors: s.errors.propertyErrors ? Object.fromEntries(
329
- Object.entries(s.errors.propertyErrors).map(([a, n]) => [
330
- E(this.path, a),
331
- n
334
+ Object.entries(s.errors.propertyErrors).map(([a, o]) => [
335
+ F(this.path, a),
336
+ o
332
337
  ])
333
338
  ) : {}
334
339
  }
@@ -336,49 +341,49 @@ class ir {
336
341
  }
337
342
  }
338
343
  function nr(e, r, t) {
339
- const s = z(e.data, r), a = d(() => w(e.initialData.value, r)), n = (i) => ({
340
- ...i,
341
- path: d(() => 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 + ".", "")),
342
347
  setData: (h) => {
343
- i.setData(h);
348
+ n.setData(h);
344
349
  }
345
- }), o = (i) => {
346
- const h = E(r, i), g = e.getField(h);
347
- return g ? n(g) : {};
348
- }, v = (i) => {
349
- const h = E(r, i.path), g = e.defineField({
350
- ...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,
351
356
  path: h
352
357
  });
353
- return n(g);
354
- }, l = () => e.getFields().filter((i) => {
355
- const h = i.path.value;
358
+ return o(g);
359
+ }, i = () => e.getFields().filter((n) => {
360
+ const h = n.path.value;
356
361
  return h.startsWith(r + ".") || h === r;
357
- }).map((i) => n(i)), c = () => e.getFields().filter((i) => {
358
- const h = i.path.value;
362
+ }).map((n) => o(n)), l = () => e.getFields().filter((n) => {
363
+ const h = n.path.value;
359
364
  return h.startsWith(r + ".") || h === r;
360
- }), f = d(() => c().some((i) => i.dirty.value)), V = d(() => c().some((i) => i.touched.value)), R = d(() => e.isValid.value), M = d(() => e.isValidated.value), $ = d(() => 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));
361
366
  return {
362
367
  data: s,
363
368
  initialData: a,
364
- defineField: v,
365
- getField: o,
366
- getFields: l,
369
+ defineField: c,
370
+ getField: v,
371
+ getFields: i,
367
372
  isDirty: f,
368
- isTouched: V,
373
+ isTouched: p,
369
374
  isValid: R,
370
375
  isValidated: M,
371
376
  errors: $,
372
- defineValidator: (i) => {
373
- const h = _(i) ? i : b(i), g = d(
374
- () => new ir(r, u(h))
377
+ defineValidator: (n) => {
378
+ const h = _(n) ? n : b(n), g = u(
379
+ () => new ir(r, d(h))
375
380
  );
376
381
  return e.defineValidator(g), h;
377
382
  },
378
- reset: () => c().forEach((i) => i.reset()),
383
+ reset: () => l().forEach((n) => n.reset()),
379
384
  validateForm: () => e.validateForm(),
380
- getSubForm: (i, h) => {
381
- const g = E(r, i);
385
+ getSubForm: (n, h) => {
386
+ const g = F(r, n);
382
387
  return e.getSubForm(
383
388
  g,
384
389
  h
@@ -386,28 +391,32 @@ function nr(e, r, t) {
386
391
  }
387
392
  };
388
393
  }
389
- function vr(e) {
390
- const r = d(() => 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({
391
396
  initialData: r,
392
397
  data: t
393
- }), a = H(s), n = ar(s, e), o = Q(a), v = () => {
394
- t.value = y(r), a.getFields().forEach(
398
+ });
399
+ E(r, (f) => {
400
+ s.data = y(f);
401
+ });
402
+ const a = ar(s, e), o = H(s, a), v = Q(o), c = () => {
403
+ t.value = y(r), o.getFields().forEach(
395
404
  (f) => f.reset()
396
405
  );
397
406
  };
398
- function l(f, V) {
399
- return nr(c, f);
407
+ function i(f, p) {
408
+ return nr(l, f);
400
409
  }
401
- const c = {
402
- ...a,
403
- ...n,
410
+ const l = {
404
411
  ...o,
405
- reset: v,
406
- getSubForm: l,
407
- initialData: D(s, "initialData"),
408
- data: D(s, "data")
412
+ ...a,
413
+ ...v,
414
+ reset: c,
415
+ getSubForm: i,
416
+ initialData: S(s, "initialData"),
417
+ data: S(s, "data")
409
418
  };
410
- return c;
419
+ return l;
411
420
  }
412
421
  const Vr = /* @__PURE__ */ B({
413
422
  __name: "Field",
@@ -421,11 +430,11 @@ const Vr = /* @__PURE__ */ B({
421
430
  setup(e) {
422
431
  const r = e, t = r.form.defineField({
423
432
  path: r.path
424
- });
425
- 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)));
426
435
  }
427
436
  });
428
437
  export {
429
438
  Vr as Field,
430
- vr as useForm
439
+ pr as useForm
431
440
  };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.0.12",
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)
@@ -1,4 +1,4 @@
1
- import { computed, reactive, ref, toRef, type MaybeRef, type MaybeRefOrGetter, type Ref } from 'vue'
1
+ import { computed, reactive, ref, toRef, watch, type MaybeRef, type MaybeRefOrGetter, type Ref } from 'vue'
2
2
  import type { AnyField, Form, FormDataDefault } from '../types/form'
3
3
  import type { EntityPaths, PickEntity } from '../types/util'
4
4
  import type { ValidationStrategy } from '../types/validation'
@@ -7,6 +7,7 @@ import { useFieldRegistry } from './useFieldRegistry'
7
7
  import { useFormState } from './useFormState'
8
8
  import { createSubformInterface, type SubformOptions } from './useSubform'
9
9
  import { useValidation, type ValidationOptions } from './useValidation'
10
+ import { syncRef } from '@vueuse/core'
10
11
 
11
12
  // TODO @Elias implement validation strategy handling
12
13
 
@@ -22,12 +23,16 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
22
23
  const data = ref<T>(cloneRefValue(initialData)) as Ref<T>
23
24
 
24
25
  const state = reactive({
25
- initialData: initialData,
26
+ initialData,
26
27
  data,
27
28
  })
28
29
 
29
- const fields = useFieldRegistry(state)
30
+ watch(initialData, (newValue) => {
31
+ state.data = cloneRefValue(newValue)
32
+ })
33
+
30
34
  const validationState = useValidation(state, options)
35
+ const fields = useFieldRegistry(state, validationState)
31
36
  const formState = useFormState(fields)
32
37
 
33
38
  const reset = () => {
@@ -195,6 +195,6 @@ describe('Integration Tests', () => {
195
195
 
196
196
  // Try to get non-existent field
197
197
  const nonExistentField = form.getField('nonexistent')
198
- expect(nonExistentField).toBeUndefined()
198
+ expect(nonExistentField).toBeDefined()
199
199
  })
200
200
  })
@@ -540,16 +540,14 @@ describe('Nested Path Handling', () => {
540
540
  })
541
541
 
542
542
  describe('Performance and Memory', () => {
543
- it('should replace field when defining same path twice', () => {
543
+ it('should return the same field when defining same path twice', () => {
544
544
  const form = useForm({ initialData })
545
545
 
546
546
  const field1 = form.defineField({ path: 'user.name' })
547
547
  const field2 = form.defineField({ path: 'user.name' })
548
548
 
549
- // Fields are not the same object, but second field replaces the first
550
- expect(field1).not.toBe(field2)
551
- expect(form.getField('user.name')).toBe(field2)
552
- expect(form.getFields().length).toBe(1)
549
+ // Fields are the same object
550
+ expect(field1).toBe(field2)
553
551
  })
554
552
 
555
553
  it('should handle large numbers of nested fields', () => {
@@ -40,6 +40,31 @@ describe('useForm', () => {
40
40
  })
41
41
  })
42
42
 
43
+ it('should reinitialize form with new initial data', async () => {
44
+ const initialData = ref({
45
+ name: 'John',
46
+ age: 30,
47
+ })
48
+ const form = useForm({ initialData })
49
+
50
+ expect(form.data.value).toEqual({
51
+ name: 'John',
52
+ age: 30,
53
+ })
54
+
55
+ // Update reactive initial data
56
+ initialData.value = {
57
+ name: 'Jane',
58
+ age: 25,
59
+ }
60
+ await nextTick()
61
+
62
+ expect(form.data.value).toEqual({
63
+ name: 'Jane',
64
+ age: 25,
65
+ })
66
+ })
67
+
43
68
  it('should have initial state values', () => {
44
69
  const form = useForm({ initialData: { name: 'John' } })
45
70
 
@@ -81,13 +106,6 @@ describe('useForm', () => {
81
106
  expect(retrievedField).toBe(nameField)
82
107
  })
83
108
 
84
- it('should return undefined for non-existent fields', () => {
85
- const form = useForm({ initialData: { name: 'John' } })
86
-
87
- const field = form.getField('nonexistent')
88
- expect(field).toBeUndefined()
89
- })
90
-
91
109
  it('should handle nested object initial data', () => {
92
110
  const initialData = {
93
111
  user: {
@@ -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
  })