@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.
@@ -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 X = Object.defineProperty;
2
- var Y = (r, t, e) => t in r ? X(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
- var R = (r, t, e) => Y(r, typeof t != "symbol" ? t + "" : t, e);
4
- import { toValue as ee, toRaw as te, unref as h, shallowRef as $, watch as D, computed as m, isRef as _, reactive as A, toRefs as K, shallowReactive as re, toRef as j, onScopeDispose as ae, triggerRef as se, ref as L, getCurrentScope as ne, onBeforeUnmount as oe, defineComponent as T, renderSlot as k, normalizeProps as x, guardReactiveProps as C, createBlock as z, openBlock as H, withCtx as U, resolveDynamicComponent as ie, mergeProps as le, createSlots as ce, renderList as ue } from "vue";
5
- import { cloneDeep as de } from "lodash-es";
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 g(r) {
8
- const t = ee(r), e = te(t);
9
- return de(e);
7
+ function E(r) {
8
+ const t = te(r), e = re(t);
9
+ return fe(e);
10
10
  }
11
- function fe(r) {
11
+ function he(r) {
12
12
  return r.filter(
13
13
  (t, e, a) => a.indexOf(t) === e
14
14
  );
15
15
  }
16
- function J(...r) {
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 fe(s);
27
- }, r[0]);
26
+ return he(s);
27
+ }, r[0]) ?? [];
28
28
  }
29
- function he(...r) {
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(Boolean);
31
+ const s = r.map((n) => n[a]).filter((n) => !!n);
32
32
  return {
33
33
  ...e,
34
- [a]: J(...s)
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: J(e.general, a.general),
48
- propertyErrors: he(e.propertyErrors ?? {}, a.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 G(r) {
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 N(r) {
59
- return !G(r.errors);
58
+ function z(r) {
59
+ return !q(r.errors);
60
60
  }
61
- function Z(r, t) {
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 (!N(n))
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 pe {
74
+ class me {
75
75
  constructor(t = (e) => e) {
76
- R(this, "weakMap", /* @__PURE__ */ new WeakMap());
77
- R(this, "map", /* @__PURE__ */ new Map());
78
- R(this, "hashFn");
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 ve(r, t, e) {
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) ?? []], v = u.findIndex((p) => !a.has(p)), c = (v === -1 ? [] : u.slice(v))[0];
101
- if (c)
102
- return a.add(c), {
103
- id: c,
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 q(r, t, e) {
116
- const a = new pe(e == null ? void 0 : e.hashFn), s = r.getField(t), n = $([]);
117
- return D(
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
- (c) => {
120
- n.value = ve(a, c, t);
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: (c) => {
128
+ push: (l) => {
129
129
  const o = s.data.value ?? [];
130
- return s.setData([...o, c]), n.value.at(-1);
130
+ return s.setData([...o, l]), n.value.at(-1);
131
131
  },
132
- remove: (c) => {
133
- const o = s.data.value ?? [], p = n.value.findIndex(
134
- ({ id: i }) => i === c
132
+ remove: (l) => {
133
+ const o = s.data.value ?? [], v = n.value.findIndex(
134
+ ({ id: i }) => i === l
135
135
  );
136
- p !== -1 && s.setData(
137
- o.slice(0, p).concat(o.slice(p + 1))
136
+ v !== -1 && s.setData(
137
+ o.slice(0, v).concat(o.slice(v + 1))
138
138
  );
139
139
  },
140
- insert: (c, o) => {
141
- const p = s.data.value ?? [];
140
+ insert: (l, o) => {
141
+ const v = s.data.value ?? [];
142
142
  return s.setData(
143
- p.slice(0, o).concat([c]).concat(p.slice(o))
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 W(r) {
149
+ function H(r) {
150
150
  return r === "" ? [] : r.split(/\s*\.\s*/).filter(Boolean);
151
151
  }
152
- function me(r, t) {
153
- const e = Array.isArray(t) ? t : W(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 : W(t)).reduce(
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 : W(t), s = a.at(-1);
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, v) => ((u == null ? void 0 : u[v]) === void 0 && (u[v] = {}), u == null ? void 0 : u[v]),
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 (!_(r))
172
+ if (!T(r))
173
173
  return;
174
174
  r.value = e;
175
175
  }
176
176
  }
177
- const Q = (r, t) => m({
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 Fe(r, t) {
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
- R(this, "rc", 1);
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 ge(r) {
215
+ function we(r) {
216
216
  const e = {
217
217
  ...{
218
218
  existsInForm: !0
219
219
  },
220
220
  ...r
221
- }, a = $(Object.freeze(g(e.initialValue))), s = A({
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
- D(
228
+ P(
229
229
  $(e.initialValue),
230
230
  () => {
231
- a.value = Object.freeze(g(e.initialValue)), s.value !== h(e.initialValue) && (s.value = g(e.initialValue));
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
- const n = m(() => JSON.stringify(s.value) !== JSON.stringify(s.initialValue)), u = (f) => {
236
- s.value = f;
237
- }, v = () => {
238
- var f;
239
- s.touched = !0, s.errors = [], (f = e.onBlur) == null || f.call(e);
240
- }, F = () => {
241
- var f;
242
- (f = e.onFocus) == null || f.call(e);
243
- }, c = () => {
244
- const f = s.path.split(".").at(-1) || "";
245
- h(e.existsInForm) && !/^\d+$/.test(f) && (s.value = g(s.initialValue)), s.touched = !1, s.errors = [];
246
- }, o = (f) => {
247
- n.value || u(g(f)), s.initialValue = f;
248
- }, p = (f) => {
249
- s.errors = f;
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
- }, l = K(s);
255
+ }, c = J(s);
253
256
  return {
254
- data: l.value,
255
- path: l.path,
256
- initialValue: l.initialValue,
257
- errors: l.errors,
258
- touched: l.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: v,
263
- onFocus: F,
264
- reset: c,
265
- setErrors: p,
265
+ onBlur: m,
266
+ onFocus: y,
267
+ reset: l,
268
+ setErrors: v,
266
269
  clearErrors: i
267
270
  };
268
271
  }
269
- const we = {
272
+ const Ee = {
270
273
  keepValuesOnUnmount: !0
271
274
  };
272
- function Ee(r, t) {
275
+ function De(r, t) {
273
276
  const e = () => b(r.initialData, t), a = $(e());
274
- return D(
277
+ return P(
275
278
  () => r.initialData,
276
279
  () => {
277
- a.value = e(), se(a);
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 = re(/* @__PURE__ */ new Map()), n = {
284
- ...we,
286
+ const a = /* @__PURE__ */ new Map(), s = ae(/* @__PURE__ */ new Map()), n = {
287
+ ...Ee,
285
288
  ...e
286
289
  }, u = (i) => {
287
- const l = h(i.path);
288
- s.set(l, i);
289
- }, v = (i) => {
290
- var l;
291
- n != null && n.keepValuesOnUnmount || (l = s.get(i)) == null || l.reset(), s.delete(i);
292
- }, F = (i) => {
293
- var l;
294
- a.has(i) ? (l = a.get(i)) == null || l.inc() : a.set(i, new Ve(() => v(i)));
295
- }, c = (i) => {
296
- var l;
297
- a.has(i) && ((l = a.get(i)) == null || l.dec());
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: l } = i;
300
- if (!s.has(l)) {
301
- const S = ge({
302
- path: l,
303
- value: Q(j(r, "data"), l),
304
- initialValue: Ee(r, l),
305
- existsInForm: m(() => me(r.data, h(l))),
306
- errors: m({
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[l] || [];
311
+ return t.errors.value.propertyErrors[c] || [];
309
312
  },
310
- set(w) {
311
- t.errors.value.propertyErrors[l] = w;
313
+ set(V) {
314
+ t.errors.value.propertyErrors[c] = V;
312
315
  }
313
316
  }),
314
317
  onBlur: async () => {
315
- var w, P;
318
+ var V, w;
316
319
  await Promise.all([
317
- (w = n == null ? void 0 : n.onBlur) == null ? void 0 : w.call(n, h(l)),
318
- (P = i.onBlur) == null ? void 0 : P.call(i)
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 w, P;
325
+ var V, w;
323
326
  await Promise.all([
324
- (w = n == null ? void 0 : n.onFocus) == null ? void 0 : w.call(n, h(l)),
325
- (P = i.onFocus) == null ? void 0 : P.call(i)
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(S);
339
+ u(D);
330
340
  }
331
- const f = s.get(l);
332
- return F(l), ae(() => {
333
- c(l);
334
- }), f;
335
- }, p = (i) => o(i);
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: m(() => [...s.values()]),
347
+ fields: p(() => [...s.values()]),
338
348
  getField: (i) => o({ path: i }),
339
349
  registerField: u,
340
- deregisterField: v,
341
- defineField: p
350
+ deregisterField: m,
351
+ defineField: v
342
352
  };
343
353
  }
344
- function De(r) {
345
- const t = m(() => r.fields.value.some((a) => h(a.dirty))), e = m(() => r.fields.value.some((a) => h(a.touched)));
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 Pe(r) {
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 V = {
374
+ const g = {
365
375
  errors: {
366
376
  general: [],
367
377
  propertyErrors: {}
368
378
  }
369
379
  };
370
- class Se {
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 V;
386
+ return g;
377
387
  const e = await this.schema.safeParseAsync(t);
378
388
  if (e.success)
379
- return V;
380
- const a = Pe(e.error);
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 be {
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 V;
406
+ return g;
397
407
  try {
398
408
  const e = await this.validateFn(t);
399
- return N(e) ? V : e;
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 Ie {
420
+ class $e {
411
421
  constructor(t, e) {
412
- R(this, "schemaValidator");
413
- R(this, "functionValidator");
414
- this.schema = t, this.validateFn = e, this.schemaValidator = new Se(this.schema), this.functionValidator = new be(this.validateFn);
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 O(r) {
428
- return m(() => new Ie(
437
+ function _(r) {
438
+ return p(() => new $e(
429
439
  h(r.schema),
430
440
  h(r.validateFn)
431
441
  ));
432
442
  }
433
- function $e(r, t) {
443
+ function Ce(r, t) {
434
444
  const e = A({
435
- validators: L([O(t)]),
445
+ validators: G([_(t)]),
436
446
  isValidated: !1,
437
- errors: h(t.errors) ?? V.errors
438
- }), a = (o = V.errors) => {
439
- e.errors = B(h(t.errors) ?? V.errors, o);
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
- D(() => h(t.errors), async () => {
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 }), D(
457
+ }, { immediate: !0 }), P(
448
458
  [() => e.validators],
449
459
  async (o) => {
450
460
  if (e.isValidated)
451
461
  if (o) {
452
- const p = await n();
453
- e.errors = p.errors;
462
+ const v = await n();
463
+ e.errors = v.errors;
454
464
  } else
455
- e.errors = V.errors;
465
+ e.errors = g.errors;
456
466
  },
457
467
  { immediate: !0 }
458
- ), D([() => r.data, () => h(t.schema)], () => {
468
+ ), P([() => r.data, () => h(t.schema)], () => {
459
469
  e.isValidated && u();
460
470
  });
461
471
  const s = (o) => {
462
- const p = _(o) ? o : O(o);
463
- return e.validators.push(p), ne() && oe(() => {
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 !== p
475
+ (i) => i !== v
466
476
  );
467
- }), p;
477
+ }), v;
468
478
  };
469
479
  async function n() {
470
- var l;
480
+ var c;
471
481
  const o = await Promise.all(
472
- e.validators.filter((f) => h(f) !== void 0).map((f) => h(f).validate(r.data))
473
- ), p = o.every((f) => N(f));
474
- let { errors: i } = V;
475
- if (!p) {
476
- const f = o.map((S) => S.errors);
477
- i = B(...f);
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: (l = o.findLast((f) => !!f.data)) == null ? void 0 : l.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
- }, v = async (o) => {
491
- const p = await n();
500
+ }, m = async (o) => {
501
+ if (!e.isValidated)
502
+ return g;
503
+ const v = await n();
492
504
  return a({
493
- general: p.errors.general,
505
+ general: v.errors.general,
494
506
  propertyErrors: {
495
- [o]: p.errors.propertyErrors[o]
507
+ ...e.errors.propertyErrors,
508
+ [o]: v.errors.propertyErrors[o] ?? []
496
509
  }
497
510
  }), {
498
- data: p.data,
511
+ data: v.data,
499
512
  errors: e.errors
500
513
  };
501
- }, F = m(() => !G(e.errors)), c = () => {
502
- e.isValidated = !1, e.errors = h(t.errors) ?? V.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
- ...K(e),
518
+ ...J(e),
506
519
  validateForm: u,
507
- validateField: v,
520
+ validateField: m,
508
521
  defineValidator: s,
509
- isValid: F,
510
- reset: c
522
+ isValid: y,
523
+ reset: l
511
524
  };
512
525
  }
513
- class ke {
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 V;
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 Ae(r, t, e, a) {
535
- const s = Q(r.data, t), n = m(() => b(r.initialData.value, t)), u = (d) => ({
536
- ...d,
537
- path: m(() => h(d.path).replace(t + ".", "")),
538
- setData: (y) => {
539
- d.setData(y);
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
- }), v = (d) => {
542
- const y = I(t, d), E = r.getField(y);
543
- return E ? u(E) : {};
544
- }, F = (d) => {
545
- const y = I(t, d.path), E = r.defineField({
546
- ...d,
547
- path: y
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(E);
550
- }, c = m(() => r.fields.value.filter((d) => {
551
- const y = d.path.value;
552
- return y.startsWith(t + ".") || y === t;
553
- }).map((d) => u(d))), o = () => r.fields.value.filter((d) => {
554
- const y = d.path.value;
555
- return y.startsWith(t + ".") || y === t;
556
- }), p = m(() => o().some((d) => d.dirty.value)), i = m(() => o().some((d) => d.touched.value)), l = m(() => r.isValid.value), f = m(() => r.isValidated.value), S = m(() => Fe(h(r.errors), t)), M = {
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: c,
571
+ fields: l,
559
572
  initialData: n,
560
- defineField: F,
561
- getField: v,
562
- isDirty: p,
573
+ defineField: y,
574
+ getField: m,
575
+ isDirty: v,
563
576
  isTouched: i,
564
- isValid: l,
565
- isValidated: f,
566
- errors: S,
567
- defineValidator: (d) => {
568
- const y = _(d) ? d : O(d), E = m(
569
- () => new ke(t, h(y))
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(E), y;
584
+ return r.defineValidator(R), F;
572
585
  },
573
- reset: () => o().forEach((d) => d.reset()),
586
+ reset: () => o().forEach((f) => f.reset()),
574
587
  validateForm: () => r.validateForm(),
575
- getSubForm: (d, y) => {
576
- const E = I(t, d);
588
+ getSubForm: (f, F) => {
589
+ const R = I(t, f);
577
590
  return r.getSubForm(
578
- E,
579
- y
591
+ R,
592
+ F
580
593
  );
581
594
  },
582
- submitHandler: (d) => Z(M, e ?? {})(d),
583
- getFieldArray: (d, y) => q(M, d, y)
595
+ submitHandler: (f) => O(k, e ?? {})(f),
596
+ getFieldArray: (f, F) => Q(k, f, F)
584
597
  };
585
- return M;
598
+ return k;
586
599
  }
587
600
  function xe(r) {
588
- const t = m(() => g(r.initialData)), e = L(g(t)), a = A({
601
+ const t = p(() => E(r.initialData)), e = G(E(t)), a = A({
589
602
  initialData: t,
590
603
  data: e
591
604
  });
592
- D(
605
+ P(
593
606
  t,
594
- (c) => {
595
- a.data = g(c);
607
+ (l) => {
608
+ a.data = E(l);
596
609
  },
597
610
  { flush: "sync" }
598
611
  );
599
- const s = $e(a, r), n = Re(a, s, {
612
+ const s = Ce(a, r), n = Re(a, s, {
600
613
  keepValuesOnUnmount: r.keepValuesOnUnmount,
601
- onBlur: async (c) => {
602
- h(r.validationStrategy) === "onTouch" && s.validateField(c);
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 = De(n), v = () => {
605
- e.value = g(t), s.reset();
606
- for (const c of n.fields.value)
607
- c.reset();
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 F = {
626
+ const y = {
611
627
  ...n,
612
628
  ...s,
613
629
  ...u,
614
- reset: v,
630
+ reset: m,
615
631
  initialData: j(a, "initialData"),
616
632
  data: j(a, "data"),
617
633
  validateForm: s.validateForm,
618
- submitHandler: (c) => Z(F, r)(c),
619
- getSubForm: (c, o) => Ae(F, c, r),
620
- getFieldArray: (c, o) => q(F, c, o)
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 F;
638
+ return y;
623
639
  }
624
- const Me = /* @__PURE__ */ T({
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) => k(s.$slots, "default", x(C(a)));
657
+ return (s, n) => C(s.$slots, "default", N(W(a)));
641
658
  }
642
- }), Ce = /* @__PURE__ */ T({
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) => (H(), z(Me, {
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: v }) => [
657
- (H(), z(ie(r.component), le({ ...r.componentProps, ...t.$attrs }, {
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": v,
676
+ "on-focus": m,
660
677
  "model-value": s,
661
678
  errors: a,
662
679
  name: r.path,
663
680
  "onUpdate:modelValue": n
664
- }), ce({
681
+ }), ue({
665
682
  default: U(() => [
666
- k(t.$slots, "default")
683
+ C(t.$slots, "default")
667
684
  ]),
668
685
  _: 2
669
686
  }, [
670
- ue(t.$slots, (F, c) => ({
671
- name: c,
687
+ de(t.$slots, (y, l) => ({
688
+ name: l,
672
689
  fn: U((o) => [
673
- k(t.$slots, c, x(C(o ?? {})))
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
- }), Ne = /* @__PURE__ */ T({
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 = m(() => t.form.getSubForm(t.path));
689
- return (a, s) => k(a.$slots, "default", x(C({ subform: e.value })));
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
- Me as Field,
694
- Ce as FormFieldWrapper,
695
- Ne as FormPart,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -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 extends FormDataDefault,
35
- TOut = T,
36
- >(options: UseFormOptions<T, TOut> & { schema: MaybeRef<z.ZodType<TOut, unknown>> }): Form<T, TOut>
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
- [path]: validationResults.errors.propertyErrors[path],
226
+ ...validationState.errors.propertyErrors,
227
+ [path]: validationResults.errors.propertyErrors[path] ?? [],
223
228
  },
224
229
  })
225
230
 
@@ -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
@@ -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(Boolean) as ValidationErrors[]
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(1)
335
- expect(form.errors.value.propertyErrors.email ?? []).toHaveLength(0)
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
  })