@teamnovu/kit-vue-forms 0.1.17 → 0.1.19

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.
@@ -11,7 +11,7 @@ declare const _default: <TData extends object, TPath extends EntityPaths<TData>>
11
11
  attrs: any;
12
12
  slots: {
13
13
  default?(_: {
14
- subform: Omit<Form< PickEntity<TData, TPath>>, "submitHandler">;
14
+ subform: Form< PickEntity<TData, TPath>>;
15
15
  }): any;
16
16
  };
17
17
  emit: {};
@@ -1,10 +1,31 @@
1
- import { MaybeRef, MaybeRefOrGetter } from 'vue';
2
- import { Form, FormDataDefault } from '../types/form';
1
+ import { Awaitable } from '@vueuse/core';
2
+ import { MaybeRef, MaybeRefOrGetter, Ref } from 'vue';
3
+ import { Form, FormDataDefault, FieldsTuple } from '../types/form';
4
+ import { EntityPaths, PickEntity } from '../types/util';
3
5
  import { ValidationStrategy } from '../types/validation';
4
- import { ValidationOptions } from './useValidation';
6
+ import { SubformOptions } from './useSubform';
7
+ import { ValidationOptions, ValidatorOptions } from './useValidation';
8
+ import { ErrorBag, Paths, PickProps, FormField, Validator, ValidationResult } from '..';
9
+ import { DefineFieldOptions } from './useFieldRegistry';
5
10
  export interface UseFormOptions<T extends FormDataDefault> extends ValidationOptions<T> {
6
11
  initialData: MaybeRefOrGetter<T>;
7
12
  validationStrategy?: MaybeRef<ValidationStrategy>;
8
13
  keepValuesOnUnmount?: MaybeRef<boolean>;
9
14
  }
10
- export declare function useForm<T extends FormDataDefault>(options: UseFormOptions<T>): Form<T>;
15
+ export declare function useForm<T extends FormDataDefault>(options: UseFormOptions<T>): {
16
+ submitHandler: (onSubmit: (data: T) => Awaitable<void>) => (event: SubmitEvent) => Promise<void>;
17
+ errors: Ref< ErrorBag>;
18
+ data: Ref<T, T>;
19
+ isValid: Ref<boolean>;
20
+ isValidated: Ref<boolean>;
21
+ initialData: Readonly<Ref<T, T>>;
22
+ fields: Ref< FieldsTuple<T, Paths<T>>, FieldsTuple<T, Paths<T>>>;
23
+ defineField: <P extends Paths<T>>(options: DefineFieldOptions<PickProps<T, P>, P>) => FormField<PickProps<T, P>, P>;
24
+ getField: <P extends Paths<T>>(path: P) => FormField<PickProps<T, P>, P>;
25
+ isDirty: Ref<boolean>;
26
+ isTouched: Ref<boolean>;
27
+ defineValidator: <TData extends T>(options: ValidatorOptions<TData> | Ref< Validator<TData>, Validator<TData>>) => Ref< Validator<TData> | undefined, Validator<TData> | undefined>;
28
+ reset: () => void;
29
+ validateForm: () => Promise< ValidationResult>;
30
+ getSubForm: <P extends EntityPaths<T>>(path: P, options?: SubformOptions<PickEntity<T, P>> | undefined) => Form<PickEntity<T, P>>;
31
+ };
@@ -1,5 +1,6 @@
1
1
  import { Form, FormDataDefault } from '../types/form';
2
2
  import { EntityPaths, PickEntity } from '../types/util';
3
+ import { UseFormOptions } from './useForm';
3
4
  export interface SubformOptions<_T extends FormDataDefault> {
4
5
  }
5
- export declare function createSubformInterface<T extends FormDataDefault, K extends EntityPaths<T>>(mainForm: Form<T>, path: K, _options?: SubformOptions<PickEntity<T, K>>): Omit<Form<PickEntity<T, K>>, 'submitHandler'>;
6
+ export declare function createSubformInterface<T extends FormDataDefault, K extends EntityPaths<T>>(mainForm: Omit<Form<T>, "submitHandler">, path: K, formOptions?: UseFormOptions<T>, _options?: SubformOptions<PickEntity<T, K>>): Form<PickEntity<T, K>>;
@@ -0,0 +1,8 @@
1
+ import { Awaitable, MaybeRef } from '@vueuse/core';
2
+ import { Form, FormDataDefault } from '../types/form';
3
+ import { ValidationStrategy } from '../types/validation';
4
+ interface SubmitHandlerOptions {
5
+ validationStrategy?: MaybeRef<ValidationStrategy>;
6
+ }
7
+ export declare function useSubmitHandler<T extends FormDataDefault>(form: Omit<Form<T>, 'submitHandler'>, options: SubmitHandlerOptions): (onSubmit: (data: T) => Awaitable<void>) => (event: SubmitEvent) => Promise<void>;
8
+ export {};
package/dist/index.js CHANGED
@@ -1,37 +1,37 @@
1
- var H = Object.defineProperty;
2
- var Z = (t, e, r) => e in t ? H(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
3
- var S = (t, e, r) => Z(t, typeof e != "symbol" ? e + "" : e, r);
4
- import { toValue as q, toRaw as Q, computed as v, unref as d, isRef as U, shallowRef as j, reactive as _, watch as P, toRefs as T, shallowReactive as X, toRef as O, onScopeDispose as Y, triggerRef as ee, ref as J, getCurrentScope as re, onBeforeUnmount as te, defineComponent as k, renderSlot as b, normalizeProps as x, guardReactiveProps as M, resolveComponent as ae, createBlock as z, openBlock as K, withCtx as A, resolveDynamicComponent as se, mergeProps as ne, createSlots as ie, renderList as oe } from "vue";
5
- import { cloneDeep as le } from "lodash-es";
1
+ var Q = Object.defineProperty;
2
+ var X = (t, e, r) => e in t ? Q(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
3
+ var S = (t, e, r) => X(t, typeof e != "symbol" ? e + "" : e, r);
4
+ import { toValue as Y, toRaw as ee, computed as v, unref as d, isRef as k, shallowRef as j, reactive as _, watch as P, toRefs as T, shallowReactive as re, toRef as I, onScopeDispose as te, triggerRef as ae, ref as J, getCurrentScope as se, onBeforeUnmount as ne, defineComponent as x, renderSlot as $, normalizeProps as M, guardReactiveProps as N, resolveComponent as ie, createBlock as H, openBlock as K, withCtx as B, resolveDynamicComponent as oe, mergeProps as le, createSlots as ce, renderList as ue } from "vue";
5
+ import { cloneDeep as de } from "lodash-es";
6
6
  import "zod";
7
7
  function g(t) {
8
- const e = q(t), r = Q(e);
9
- return le(r);
8
+ const e = Y(t), r = ee(e);
9
+ return de(r);
10
10
  }
11
- function N(t) {
11
+ function W(t) {
12
12
  return t === "" ? [] : t.split(/\s*\.\s*/).filter(Boolean);
13
13
  }
14
- function ce(t, e) {
15
- const r = Array.isArray(e) ? e : N(e);
14
+ function fe(t, e) {
15
+ const r = Array.isArray(e) ? e : W(e);
16
16
  return !!D(t, r.slice(0, -1));
17
17
  }
18
18
  function D(t, e) {
19
- return (Array.isArray(e) ? e : N(e)).reduce(
19
+ return (Array.isArray(e) ? e : W(e)).reduce(
20
20
  (a, s) => a == null ? void 0 : a[s],
21
21
  t
22
22
  );
23
23
  }
24
- function ue(t, e, r) {
25
- const a = Array.isArray(e) ? e : N(e), s = a.at(-1);
24
+ function he(t, e, r) {
25
+ const a = Array.isArray(e) ? e : W(e), s = a.at(-1);
26
26
  if (s) {
27
27
  const n = a.slice(0, -1).reduce(
28
- (f, m) => ((f == null ? void 0 : f[m]) === void 0 && (f[m] = {}), f == null ? void 0 : f[m]),
28
+ (f, p) => ((f == null ? void 0 : f[p]) === void 0 && (f[p] = {}), f == null ? void 0 : f[p]),
29
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
30
  d(t)
31
31
  );
32
32
  n[s] = r;
33
33
  } else {
34
- if (!U(t))
34
+ if (!k(t))
35
35
  return;
36
36
  t.value = r;
37
37
  }
@@ -41,13 +41,13 @@ const L = (t, e) => v({
41
41
  return D(d(t), d(e));
42
42
  },
43
43
  set(r) {
44
- ue(t, d(e), r);
44
+ he(t, d(e), r);
45
45
  }
46
46
  });
47
- function $(t, e) {
47
+ function b(t, e) {
48
48
  return !t && !e ? "" : !t && e ? e : !e && t ? t : `${t}.${e}`;
49
49
  }
50
- function de(t, e) {
50
+ function ve(t, e) {
51
51
  if (!e)
52
52
  return t;
53
53
  const r = `${e}.`, a = Object.fromEntries(
@@ -62,7 +62,7 @@ function de(t, e) {
62
62
  propertyErrors: a
63
63
  };
64
64
  }
65
- class fe {
65
+ class me {
66
66
  constructor(e) {
67
67
  S(this, "rc", 1);
68
68
  this.drop = e;
@@ -74,7 +74,7 @@ class fe {
74
74
  this.rc > 0 && (this.rc -= 1, this.rc === 0 && this.drop && this.drop());
75
75
  }
76
76
  }
77
- function he(t) {
77
+ function pe(t) {
78
78
  const r = {
79
79
  ...{
80
80
  existsInForm: !0
@@ -96,10 +96,10 @@ function he(t) {
96
96
  );
97
97
  const n = v(() => JSON.stringify(s.value) !== JSON.stringify(s.initialValue)), f = (h) => {
98
98
  s.value = h;
99
- }, m = () => {
99
+ }, p = () => {
100
100
  var h;
101
101
  s.touched = !0, s.errors = [], (h = r.onBlur) == null || h.call(r);
102
- }, y = () => {
102
+ }, F = () => {
103
103
  var h;
104
104
  (h = r.onFocus) == null || h.call(r);
105
105
  }, V = () => {
@@ -121,50 +121,50 @@ function he(t) {
121
121
  dirty: n,
122
122
  setData: f,
123
123
  setInitialData: l,
124
- onBlur: m,
125
- onFocus: y,
124
+ onBlur: p,
125
+ onFocus: F,
126
126
  reset: V,
127
127
  setErrors: c,
128
128
  clearErrors: o
129
129
  };
130
130
  }
131
- const ve = {
131
+ const Ve = {
132
132
  keepValuesOnUnmount: !0
133
133
  };
134
- function pe(t, e) {
134
+ function Fe(t, e) {
135
135
  const r = () => D(t.initialData, e), a = j(r());
136
136
  return P(
137
137
  () => t.initialData,
138
138
  () => {
139
- a.value = r(), ee(a);
139
+ a.value = r(), ae(a);
140
140
  },
141
141
  { flush: "sync" }
142
142
  ), a;
143
143
  }
144
- function me(t, e, r) {
145
- const a = /* @__PURE__ */ new Map(), s = X(/* @__PURE__ */ new Map()), n = {
146
- ...ve,
144
+ function ye(t, e, r) {
145
+ const a = /* @__PURE__ */ new Map(), s = re(/* @__PURE__ */ new Map()), n = {
146
+ ...Ve,
147
147
  ...r
148
148
  }, f = (o) => {
149
149
  const i = d(o.path);
150
150
  s.set(i, o);
151
- }, m = (o) => {
151
+ }, p = (o) => {
152
152
  var i;
153
153
  n != null && n.keepValuesOnUnmount || (i = s.get(o)) == null || i.reset(), s.delete(o);
154
- }, y = (o) => {
154
+ }, F = (o) => {
155
155
  var i;
156
- a.has(o) ? (i = a.get(o)) == null || i.inc() : a.set(o, new fe(() => m(o)));
156
+ a.has(o) ? (i = a.get(o)) == null || i.inc() : a.set(o, new me(() => p(o)));
157
157
  }, V = (o) => {
158
158
  var i;
159
159
  a.has(o) && ((i = a.get(o)) == null || i.dec());
160
160
  }, l = (o) => {
161
161
  const { path: i } = o;
162
162
  if (!s.has(i)) {
163
- const W = he({
163
+ const A = pe({
164
164
  path: i,
165
- value: L(O(t, "data"), i),
166
- initialValue: pe(t, i),
167
- existsInForm: v(() => ce(t.data, d(i))),
165
+ value: L(I(t, "data"), i),
166
+ initialValue: Fe(t, i),
167
+ existsInForm: v(() => fe(t.data, d(i))),
168
168
  errors: v({
169
169
  get() {
170
170
  return e.errors.value.propertyErrors[i] || [];
@@ -188,10 +188,10 @@ function me(t, e, r) {
188
188
  ]);
189
189
  }
190
190
  });
191
- f(W);
191
+ f(A);
192
192
  }
193
193
  const h = s.get(i);
194
- return y(i), Y(() => {
194
+ return F(i), te(() => {
195
195
  V(i);
196
196
  }), h;
197
197
  }, c = (o) => l(o);
@@ -199,23 +199,28 @@ function me(t, e, r) {
199
199
  fields: v(() => [...s.values()]),
200
200
  getField: (o) => l({ path: o }),
201
201
  registerField: f,
202
- deregisterField: m,
202
+ deregisterField: p,
203
203
  defineField: c
204
204
  };
205
205
  }
206
- function Ve(t) {
206
+ function ge(t) {
207
207
  const e = v(() => t.fields.value.some((a) => d(a.dirty))), r = v(() => t.fields.value.some((a) => d(a.touched)));
208
208
  return {
209
209
  isDirty: e,
210
210
  isTouched: r
211
211
  };
212
212
  }
213
- function ye(t) {
213
+ function G(t, e) {
214
+ return (r) => async (a) => {
215
+ a.preventDefault(), d(e.validationStrategy) !== "none" && await t.validateForm(), t.isValid.value && await r(t.data.value);
216
+ };
217
+ }
218
+ function Ee(t) {
214
219
  return t.filter(
215
220
  (e, r, a) => a.indexOf(e) === r
216
221
  );
217
222
  }
218
- function G(...t) {
223
+ function Z(...t) {
219
224
  return t.slice(1).reduce((e, r) => {
220
225
  if (!e && !r)
221
226
  return;
@@ -225,15 +230,15 @@ function G(...t) {
225
230
  if (!a)
226
231
  return e;
227
232
  const s = (e ?? []).concat(r);
228
- return ye(s);
233
+ return Ee(s);
229
234
  }, t[0]);
230
235
  }
231
- function Fe(...t) {
236
+ function we(...t) {
232
237
  return t.map((r) => Object.keys(r)).flat().reduce((r, a) => {
233
238
  const s = t.map((n) => n[a]).filter(Boolean);
234
239
  return {
235
240
  ...r,
236
- [a]: G(...s)
241
+ [a]: Z(...s)
237
242
  };
238
243
  }, {});
239
244
  }
@@ -246,18 +251,18 @@ function C(...t) {
246
251
  const e = t[0];
247
252
  return t.length === 1 ? e : t.slice(1).reduce(
248
253
  (r, a) => ({
249
- general: G(r.general, a.general),
250
- propertyErrors: Fe(r.propertyErrors ?? {}, a.propertyErrors ?? {})
254
+ general: Z(r.general, a.general),
255
+ propertyErrors: we(r.propertyErrors ?? {}, a.propertyErrors ?? {})
251
256
  }),
252
257
  e
253
258
  );
254
259
  }
255
- function B(t) {
260
+ function O(t) {
256
261
  var a;
257
262
  const e = (((a = t.general) == null ? void 0 : a.length) ?? 0) > 0, r = Object.entries(t.propertyErrors).filter(([, s]) => s == null ? void 0 : s.length).length > 0;
258
263
  return e || r;
259
264
  }
260
- function ge(t) {
265
+ function Re(t) {
261
266
  const e = t.issues.filter((a) => a.path.length === 0).map((a) => a.message), r = t.issues.filter((a) => a.path.length > 0).reduce((a, s) => {
262
267
  const n = s.path.join(".");
263
268
  return {
@@ -270,24 +275,24 @@ function ge(t) {
270
275
  propertyErrors: r
271
276
  };
272
277
  }
273
- const F = {
278
+ const y = {
274
279
  isValid: !0,
275
280
  errors: {
276
281
  general: [],
277
282
  propertyErrors: {}
278
283
  }
279
284
  };
280
- class Ee {
285
+ class Pe {
281
286
  constructor(e) {
282
287
  this.schema = e;
283
288
  }
284
289
  async validate(e) {
285
290
  if (!this.schema)
286
- return F;
291
+ return y;
287
292
  const r = await this.schema.safeParseAsync(e);
288
293
  if (r.success)
289
- return F;
290
- const a = ge(r.error);
294
+ return y;
295
+ const a = Re(r.error);
291
296
  return {
292
297
  isValid: !1,
293
298
  errors: {
@@ -297,16 +302,16 @@ class Ee {
297
302
  };
298
303
  }
299
304
  }
300
- class we {
305
+ class De {
301
306
  constructor(e) {
302
307
  this.validateFn = e;
303
308
  }
304
309
  async validate(e) {
305
310
  if (!this.validateFn)
306
- return F;
311
+ return y;
307
312
  try {
308
313
  const r = await this.validateFn(e);
309
- return r.isValid ? F : r;
314
+ return r.isValid ? y : r;
310
315
  } catch (r) {
311
316
  return {
312
317
  isValid: !1,
@@ -318,11 +323,11 @@ class we {
318
323
  }
319
324
  }
320
325
  }
321
- class Re {
326
+ class Se {
322
327
  constructor(e, r) {
323
328
  S(this, "schemaValidator");
324
329
  S(this, "functionValidator");
325
- this.schema = e, this.validateFn = r, this.schemaValidator = new Ee(this.schema), this.functionValidator = new we(this.validateFn);
330
+ this.schema = e, this.validateFn = r, this.schemaValidator = new Pe(this.schema), this.functionValidator = new De(this.validateFn);
326
331
  }
327
332
  async validate(e) {
328
333
  const [r, a] = await Promise.all([
@@ -335,19 +340,19 @@ class Re {
335
340
  };
336
341
  }
337
342
  }
338
- function I(t) {
339
- return v(() => new Re(
343
+ function U(t) {
344
+ return v(() => new Se(
340
345
  d(t.schema),
341
346
  d(t.validateFn)
342
347
  ));
343
348
  }
344
- function Pe(t, e) {
349
+ function be(t, e) {
345
350
  const r = _({
346
- validators: J([I(e)]),
351
+ validators: J([U(e)]),
347
352
  isValidated: !1,
348
- errors: d(e.errors) ?? F.errors
349
- }), a = (l = F.errors) => {
350
- r.errors = C(d(e.errors) ?? F.errors, l);
353
+ errors: d(e.errors) ?? y.errors
354
+ }), a = (l = y.errors) => {
355
+ r.errors = C(d(e.errors) ?? y.errors, l);
351
356
  };
352
357
  P(() => d(e.errors), async () => {
353
358
  if (r.isValidated) {
@@ -363,15 +368,15 @@ function Pe(t, e) {
363
368
  const c = await n();
364
369
  r.errors = c.errors;
365
370
  } else
366
- r.errors = F.errors;
371
+ r.errors = y.errors;
367
372
  },
368
373
  { immediate: !0 }
369
374
  ), P([() => t.data, () => d(e.schema)], () => {
370
375
  r.isValidated && f();
371
376
  });
372
377
  const s = (l) => {
373
- const c = U(l) ? l : I(l);
374
- return r.validators.push(c), re() && te(() => {
378
+ const c = k(l) ? l : U(l);
379
+ return r.validators.push(c), se() && ne(() => {
375
380
  r.validators = r.validators.filter(
376
381
  (o) => o !== c
377
382
  );
@@ -381,7 +386,7 @@ function Pe(t, e) {
381
386
  const l = await Promise.all(
382
387
  r.validators.filter((i) => d(i) !== void 0).map((i) => d(i).validate(t.data))
383
388
  ), c = l.every((i) => i.isValid);
384
- let { errors: o } = F;
389
+ let { errors: o } = y;
385
390
  if (!c) {
386
391
  const i = l.map((h) => h.errors);
387
392
  o = C(...i);
@@ -394,10 +399,10 @@ function Pe(t, e) {
394
399
  const f = async () => {
395
400
  const l = await n();
396
401
  return a(l.errors), r.isValidated = !0, {
397
- isValid: !B(l.errors),
402
+ isValid: !O(l.errors),
398
403
  errors: r.errors
399
404
  };
400
- }, m = async (l) => {
405
+ }, p = async (l) => {
401
406
  const c = await n();
402
407
  return a({
403
408
  general: c.errors.general,
@@ -405,96 +410,104 @@ function Pe(t, e) {
405
410
  [l]: c.errors.propertyErrors[l]
406
411
  }
407
412
  }), {
408
- isValid: !B(c.errors),
413
+ isValid: !O(c.errors),
409
414
  errors: r.errors
410
415
  };
411
- }, y = v(() => !B(r.errors)), V = () => {
412
- r.isValidated = !1, r.errors = d(e.errors) ?? F.errors;
416
+ }, F = v(() => !O(r.errors)), V = () => {
417
+ r.isValidated = !1, r.errors = d(e.errors) ?? y.errors;
413
418
  };
414
419
  return {
415
420
  ...T(r),
416
421
  validateForm: f,
417
- validateField: m,
422
+ validateField: p,
418
423
  defineValidator: s,
419
- isValid: y,
424
+ isValid: F,
420
425
  reset: V
421
426
  };
422
427
  }
423
- class De {
428
+ class $e {
424
429
  constructor(e, r) {
425
430
  this.path = e, this.validator = r;
426
431
  }
427
432
  async validate(e) {
428
433
  const r = D(e, this.path);
429
434
  if (!this.validator)
430
- return F;
435
+ return y;
431
436
  const a = await this.validator.validate(r);
432
437
  return {
433
438
  isValid: a.isValid,
434
439
  errors: {
435
440
  general: a.errors.general || [],
436
441
  propertyErrors: a.errors.propertyErrors ? Object.fromEntries(
437
- Object.entries(a.errors.propertyErrors).map(([s, n]) => [
438
- $(this.path, s),
439
- n
440
- ])
442
+ Object.entries(a.errors.propertyErrors).map(
443
+ ([s, n]) => [b(this.path, s), n]
444
+ )
441
445
  ) : {}
442
446
  }
443
447
  };
444
448
  }
445
449
  }
446
- function Se(t, e, r) {
447
- const a = L(t.data, e), s = v(() => D(t.initialData.value, e)), n = (u) => ({
450
+ function _e(t, e, r, a) {
451
+ const s = L(t.data, e), n = v(() => D(t.initialData.value, e)), f = (u) => ({
448
452
  ...u,
449
453
  path: v(() => d(u.path).replace(e + ".", "")),
450
- setData: (p) => {
451
- u.setData(p);
454
+ setData: (m) => {
455
+ u.setData(m);
452
456
  }
453
- }), f = (u) => {
454
- const p = $(e, u), w = t.getField(p);
455
- return w ? n(w) : {};
456
- }, m = (u) => {
457
- const p = $(e, u.path), w = t.defineField({
457
+ }), p = (u) => {
458
+ const m = b(e, u), w = t.getField(m);
459
+ return w ? f(w) : {};
460
+ }, F = (u) => {
461
+ const m = b(e, u.path), w = t.defineField({
458
462
  ...u,
459
- path: p
463
+ path: m
460
464
  });
461
- return n(w);
462
- }, y = v(() => t.fields.value.filter((u) => {
463
- const p = u.path.value;
464
- return p.startsWith(e + ".") || p === e;
465
- }).map((u) => n(u))), V = () => t.fields.value.filter((u) => {
466
- const p = u.path.value;
467
- return p.startsWith(e + ".") || p === e;
468
- }), l = v(() => V().some((u) => u.dirty.value)), c = v(() => V().some((u) => u.touched.value)), o = v(() => t.isValid.value), i = v(() => t.isValidated.value), h = v(() => de(d(t.errors), e));
469
- return {
470
- data: a,
471
- fields: y,
472
- initialData: s,
473
- defineField: m,
474
- getField: f,
475
- isDirty: l,
476
- isTouched: c,
477
- isValid: o,
478
- isValidated: i,
479
- errors: h,
465
+ return f(w);
466
+ }, V = v(() => t.fields.value.filter((u) => {
467
+ const m = u.path.value;
468
+ return m.startsWith(e + ".") || m === e;
469
+ }).map((u) => f(u))), l = () => t.fields.value.filter((u) => {
470
+ const m = u.path.value;
471
+ return m.startsWith(e + ".") || m === e;
472
+ }), c = v(
473
+ () => l().some((u) => u.dirty.value)
474
+ ), o = v(
475
+ () => l().some((u) => u.touched.value)
476
+ ), i = v(() => t.isValid.value), h = v(() => t.isValidated.value), A = v(
477
+ () => ve(d(t.errors), e)
478
+ ), z = {
479
+ data: s,
480
+ fields: V,
481
+ initialData: n,
482
+ defineField: F,
483
+ getField: p,
484
+ isDirty: c,
485
+ isTouched: o,
486
+ isValid: i,
487
+ isValidated: h,
488
+ errors: A,
480
489
  defineValidator: (u) => {
481
- const p = U(u) ? u : I(u), w = v(
482
- () => new De(e, d(p))
490
+ const m = k(u) ? u : U(u), w = v(
491
+ () => new $e(e, d(m))
483
492
  );
484
- return t.defineValidator(w), p;
493
+ return t.defineValidator(w), m;
485
494
  },
486
- reset: () => V().forEach((u) => u.reset()),
495
+ reset: () => l().forEach((u) => u.reset()),
487
496
  validateForm: () => t.validateForm(),
488
- getSubForm: (u, p) => {
489
- const w = $(e, u);
497
+ getSubForm: (u, m) => {
498
+ const w = b(e, u);
490
499
  return t.getSubForm(
491
500
  w,
492
- p
501
+ m
493
502
  );
494
503
  }
504
+ }, q = G(z, r ?? {});
505
+ return {
506
+ ...z,
507
+ submitHandler: q
495
508
  };
496
509
  }
497
- function je(t) {
510
+ function Ue(t) {
498
511
  const e = v(() => g(t.initialData)), r = J(g(e)), a = _({
499
512
  initialData: e,
500
513
  data: r
@@ -506,34 +519,34 @@ function je(t) {
506
519
  },
507
520
  { flush: "sync" }
508
521
  );
509
- const s = Pe(a, t), n = me(a, s, {
522
+ const s = be(a, t), n = ye(a, s, {
510
523
  keepValuesOnUnmount: t.keepValuesOnUnmount,
511
524
  onBlur: async (c) => {
512
525
  d(t.validationStrategy) === "onTouch" && s.validateField(c);
513
526
  }
514
- }), f = Ve(n), m = (c) => async (o) => {
515
- o.preventDefault(), d(t.validationStrategy) !== "none" && await s.validateForm(), s.isValid.value && await c(a.data);
516
- }, y = () => {
527
+ }), f = ge(n), p = () => {
517
528
  r.value = g(e), s.reset();
518
529
  for (const c of n.fields.value)
519
530
  c.reset();
520
531
  };
521
- function V(c, o) {
522
- return Se(l, c);
532
+ function F(c, o) {
533
+ return _e(V, c, t);
523
534
  }
524
- const l = {
535
+ const V = {
525
536
  ...n,
526
537
  ...s,
527
538
  ...f,
528
- reset: y,
529
- getSubForm: V,
530
- submitHandler: m,
531
- initialData: O(a, "initialData"),
532
- data: O(a, "data")
539
+ reset: p,
540
+ getSubForm: F,
541
+ initialData: I(a, "initialData"),
542
+ data: I(a, "data")
543
+ }, l = G(V, t);
544
+ return d(t.validationStrategy) === "onFormOpen" && s.validateForm(), {
545
+ ...V,
546
+ submitHandler: l
533
547
  };
534
- return d(t.validationStrategy) === "onFormOpen" && s.validateForm(), l;
535
548
  }
536
- const Oe = /* @__PURE__ */ k({
549
+ const ke = /* @__PURE__ */ x({
537
550
  __name: "Field",
538
551
  props: {
539
552
  form: {},
@@ -549,9 +562,9 @@ const Oe = /* @__PURE__ */ k({
549
562
  const e = t, r = e.form.defineField({
550
563
  path: e.path
551
564
  }), a = _(r);
552
- return (s, n) => b(s.$slots, "default", x(M(a)));
565
+ return (s, n) => $(s.$slots, "default", M(N(a)));
553
566
  }
554
- }), Ce = /* @__PURE__ */ k({
567
+ }), xe = /* @__PURE__ */ x({
555
568
  inheritAttrs: !1,
556
569
  __name: "FormFieldWrapper",
557
570
  props: {
@@ -562,27 +575,27 @@ const Oe = /* @__PURE__ */ k({
562
575
  },
563
576
  setup(t) {
564
577
  return (e, r) => {
565
- const a = ae("Field");
566
- return K(), z(a, {
578
+ const a = ie("Field");
579
+ return K(), H(a, {
567
580
  form: t.form,
568
581
  path: t.path
569
582
  }, {
570
- default: A(({ errors: s, data: n, setData: f }) => [
571
- (K(), z(se(t.component), ne({ ...t.componentProps, ...e.$attrs }, {
583
+ default: B(({ errors: s, data: n, setData: f }) => [
584
+ (K(), H(oe(t.component), le({ ...t.componentProps, ...e.$attrs }, {
572
585
  "model-value": n,
573
586
  errors: s,
574
587
  name: t.path,
575
588
  "onUpdate:modelValue": f
576
- }), ie({
577
- default: A(() => [
578
- b(e.$slots, "default")
589
+ }), ce({
590
+ default: B(() => [
591
+ $(e.$slots, "default")
579
592
  ]),
580
593
  _: 2
581
594
  }, [
582
- oe(e.$slots, (m, y) => ({
583
- name: y,
584
- fn: A((V) => [
585
- b(e.$slots, y, x(M(V ?? {})))
595
+ ue(e.$slots, (p, F) => ({
596
+ name: F,
597
+ fn: B((V) => [
598
+ $(e.$slots, F, M(N(V ?? {})))
586
599
  ])
587
600
  }))
588
601
  ]), 1040, ["model-value", "errors", "name", "onUpdate:modelValue"]))
@@ -591,7 +604,7 @@ const Oe = /* @__PURE__ */ k({
591
604
  }, 8, ["form", "path"]);
592
605
  };
593
606
  }
594
- }), Ie = /* @__PURE__ */ k({
607
+ }), Me = /* @__PURE__ */ x({
595
608
  __name: "FormPart",
596
609
  props: {
597
610
  form: {},
@@ -599,12 +612,12 @@ const Oe = /* @__PURE__ */ k({
599
612
  },
600
613
  setup(t) {
601
614
  const e = t, r = v(() => e.form.getSubForm(e.path));
602
- return (a, s) => b(a.$slots, "default", x(M({ subform: r.value })));
615
+ return (a, s) => $(a.$slots, "default", M(N({ subform: r.value })));
603
616
  }
604
617
  });
605
618
  export {
606
- Oe as Field,
607
- Ce as FormFieldWrapper,
608
- Ie as FormPart,
609
- je as useForm
619
+ ke as Field,
620
+ xe as FormFieldWrapper,
621
+ Me as FormPart,
622
+ Ue as useForm
610
623
  };
@@ -44,5 +44,5 @@ export interface Form<T extends FormDataDefault> {
44
44
  reset: () => void;
45
45
  validateForm: () => Promise<ValidationResult>;
46
46
  submitHandler: (onSubmit: (data: T) => Awaitable<void>) => (event: SubmitEvent) => Promise<void>;
47
- getSubForm: <P extends EntityPaths<T>>(path: P, options?: SubformOptions<PickEntity<T, P>>) => Omit<Form<PickEntity<T, P>>, 'submitHandler'>;
47
+ getSubForm: <P extends EntityPaths<T>>(path: P, options?: SubformOptions<PickEntity<T, P>>) => Form<PickEntity<T, P>>;
48
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import type { Awaitable } from '@vueuse/core'
1
+ import type { Awaitable } from "@vueuse/core";
2
2
  import {
3
3
  computed,
4
4
  reactive,
@@ -9,97 +9,86 @@ import {
9
9
  type MaybeRef,
10
10
  type MaybeRefOrGetter,
11
11
  type Ref,
12
- } from 'vue'
13
- import type { Form, FormDataDefault } from '../types/form'
14
- import type { EntityPaths, PickEntity } from '../types/util'
15
- import type { ValidationStrategy } from '../types/validation'
16
- import { cloneRefValue } from '../utils/general'
17
- import { useFieldRegistry } from './useFieldRegistry'
18
- import { useFormState } from './useFormState'
19
- import { createSubformInterface, type SubformOptions } from './useSubform'
20
- import { useValidation, type ValidationOptions } from './useValidation'
12
+ } from "vue";
13
+ import type { Form, FormDataDefault } from "../types/form";
14
+ import type { EntityPaths, PickEntity } from "../types/util";
15
+ import type { ValidationStrategy } from "../types/validation";
16
+ import { cloneRefValue } from "../utils/general";
17
+ import { useFieldRegistry } from "./useFieldRegistry";
18
+ import { useFormState } from "./useFormState";
19
+ import { createSubformInterface, type SubformOptions } from "./useSubform";
20
+ import { useValidation, type ValidationOptions } from "./useValidation";
21
+ import { useSubmitHandler } from "./useSubmitHandler";
21
22
 
22
23
  export interface UseFormOptions<T extends FormDataDefault>
23
24
  extends ValidationOptions<T> {
24
- initialData: MaybeRefOrGetter<T>
25
- validationStrategy?: MaybeRef<ValidationStrategy>
26
- keepValuesOnUnmount?: MaybeRef<boolean>
25
+ initialData: MaybeRefOrGetter<T>;
26
+ validationStrategy?: MaybeRef<ValidationStrategy>;
27
+ keepValuesOnUnmount?: MaybeRef<boolean>;
27
28
  }
28
29
 
29
30
  export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
30
- const initialData = computed(() => cloneRefValue(options.initialData))
31
+ const initialData = computed(() => cloneRefValue(options.initialData));
31
32
 
32
- const data = ref<T>(cloneRefValue(initialData)) as Ref<T>
33
+ const data = ref<T>(cloneRefValue(initialData)) as Ref<T>;
33
34
 
34
35
  const state = reactive({
35
36
  initialData,
36
37
  data,
37
- })
38
+ });
38
39
 
39
40
  watch(
40
41
  initialData,
41
42
  (newValue) => {
42
- state.data = cloneRefValue(newValue)
43
+ state.data = cloneRefValue(newValue);
43
44
  },
44
- { flush: 'sync' },
45
- )
45
+ { flush: "sync" },
46
+ );
46
47
 
47
- const validationState = useValidation(state, options)
48
+ const validationState = useValidation(state, options);
48
49
  const fieldRegistry = useFieldRegistry(state, validationState, {
49
50
  keepValuesOnUnmount: options.keepValuesOnUnmount,
50
51
  onBlur: async (path: string) => {
51
- if (unref(options.validationStrategy) === 'onTouch') {
52
- validationState.validateField(path)
52
+ if (unref(options.validationStrategy) === "onTouch") {
53
+ validationState.validateField(path);
53
54
  }
54
55
  },
55
- })
56
- const formState = useFormState(fieldRegistry)
57
-
58
- const submitHandler = (onSubmit: (data: T) => Awaitable<void>) => {
59
- return async (event: SubmitEvent) => {
60
- event.preventDefault()
61
-
62
- if (unref(options.validationStrategy) !== 'none') {
63
- await validationState.validateForm()
64
- }
65
-
66
- if (!validationState.isValid.value) {
67
- return
68
- }
69
-
70
- await onSubmit(state.data)
71
- }
72
- }
56
+ });
57
+ const formState = useFormState(fieldRegistry);
73
58
 
74
59
  const reset = () => {
75
- data.value = cloneRefValue(initialData)
76
- validationState.reset()
60
+ data.value = cloneRefValue(initialData);
61
+ validationState.reset();
77
62
  for (const field of fieldRegistry.fields.value) {
78
- field.reset()
63
+ field.reset();
79
64
  }
80
- }
65
+ };
81
66
 
82
67
  function getSubForm<K extends EntityPaths<T>>(
83
68
  path: K,
84
- options?: SubformOptions<PickEntity<T, K>>,
85
- ): Omit<Form<PickEntity<T, K>>, 'submitHandler'> {
86
- return createSubformInterface(formInterface, path, options)
69
+ subformOptions?: SubformOptions<PickEntity<T, K>>,
70
+ ): Form<PickEntity<T, K>> {
71
+ return createSubformInterface(formInterface, path, options, subformOptions);
87
72
  }
88
73
 
89
- const formInterface: Form<T> = {
74
+ const formInterface: Omit<Form<T>, "submitHandler"> = {
90
75
  ...fieldRegistry,
91
76
  ...validationState,
92
77
  ...formState,
93
78
  reset,
94
79
  getSubForm,
95
- submitHandler,
96
- initialData: toRef(state, 'initialData') as Form<T>['initialData'],
97
- data: toRef(state, 'data') as Form<T>['data'],
98
- }
80
+ initialData: toRef(state, "initialData") as Form<T>["initialData"],
81
+ data: toRef(state, "data") as Form<T>["data"],
82
+ };
83
+
84
+ const submitHandler = useSubmitHandler(formInterface, options);
99
85
 
100
- if (unref(options.validationStrategy) === 'onFormOpen') {
101
- validationState.validateForm()
86
+ if (unref(options.validationStrategy) === "onFormOpen") {
87
+ validationState.validateForm();
102
88
  }
103
89
 
104
- return formInterface
90
+ return {
91
+ ...formInterface,
92
+ submitHandler,
93
+ };
105
94
  }
@@ -1,30 +1,47 @@
1
- import { computed, isRef, unref, type Ref } from 'vue'
2
- import type { FieldsTuple, Form, FormDataDefault, FormField } from '../types/form'
3
- import type { EntityPaths, Paths, PickEntity, PickProps } from '../types/util'
4
- import type { ValidationResult, Validator } from '../types/validation'
5
- import { filterErrorsForPath, getLens, getNestedValue, joinPath } from '../utils/path'
6
- import type { DefineFieldOptions } from './useFieldRegistry'
7
- import { createValidator, SuccessValidationResult, type ValidatorOptions } from './useValidation'
1
+ import { computed, isRef, unref, type Ref } from "vue";
2
+ import type {
3
+ FieldsTuple,
4
+ Form,
5
+ FormDataDefault,
6
+ FormField,
7
+ } from "../types/form";
8
+ import type { EntityPaths, Paths, PickEntity, PickProps } from "../types/util";
9
+ import type { ValidationResult, Validator } from "../types/validation";
10
+ import {
11
+ filterErrorsForPath,
12
+ getLens,
13
+ getNestedValue,
14
+ joinPath,
15
+ } from "../utils/path";
16
+ import type { DefineFieldOptions } from "./useFieldRegistry";
17
+ import type { UseFormOptions } from "./useForm";
18
+ import { useSubmitHandler } from "./useSubmitHandler";
19
+ import {
20
+ createValidator,
21
+ SuccessValidationResult,
22
+ type ValidatorOptions,
23
+ } from "./useValidation";
8
24
 
9
25
  export interface SubformOptions<_T extends FormDataDefault> {
10
26
  // Additional subform-specific options can be added here
11
27
  }
12
28
 
13
- class NestedValidator<T extends FormDataDefault, P extends Paths<T>> implements Validator<T> {
29
+ class NestedValidator<T extends FormDataDefault, P extends Paths<T>>
30
+ implements Validator<T>
31
+ {
14
32
  constructor(
15
33
  private path: P,
16
34
  private validator: Validator<PickEntity<T, P>> | undefined,
17
- ) {
18
- }
35
+ ) {}
19
36
 
20
37
  async validate(data: T): Promise<ValidationResult> {
21
- const subFormData = getNestedValue(data, this.path) as PickEntity<T, P>
38
+ const subFormData = getNestedValue(data, this.path) as PickEntity<T, P>;
22
39
 
23
40
  if (!this.validator) {
24
- return SuccessValidationResult
41
+ return SuccessValidationResult;
25
42
  }
26
43
 
27
- const validationResult = await this.validator.validate(subFormData)
44
+ const validationResult = await this.validator.validate(subFormData);
28
45
 
29
46
  return {
30
47
  isValid: validationResult.isValid,
@@ -32,14 +49,13 @@ class NestedValidator<T extends FormDataDefault, P extends Paths<T>> implements
32
49
  general: validationResult.errors.general || [],
33
50
  propertyErrors: validationResult.errors.propertyErrors
34
51
  ? Object.fromEntries(
35
- Object.entries(validationResult.errors.propertyErrors).map(([key, errors]) => [
36
- joinPath(this.path, key),
37
- errors,
38
- ]),
52
+ Object.entries(validationResult.errors.propertyErrors).map(
53
+ ([key, errors]) => [joinPath(this.path, key), errors],
54
+ ),
39
55
  )
40
56
  : {},
41
57
  },
42
- }
58
+ };
43
59
  }
44
60
  }
45
61
 
@@ -47,21 +63,22 @@ export function createSubformInterface<
47
63
  T extends FormDataDefault,
48
64
  K extends EntityPaths<T>,
49
65
  >(
50
- mainForm: Form<T>,
66
+ mainForm: Omit<Form<T>, "submitHandler">,
51
67
  path: K,
68
+ formOptions?: UseFormOptions<T>,
52
69
  _options?: SubformOptions<PickEntity<T, K>>,
53
- ): Omit<Form<PickEntity<T, K>>, 'submitHandler'> {
54
- type ST = PickEntity<T, K>
55
- type SP = Paths<ST>
56
- type MP<P extends SP> = `${K}.${P}`
57
- type ScopedMainPaths = Paths<T> & MP<SP>
70
+ ): Form<PickEntity<T, K>> {
71
+ type ST = PickEntity<T, K>;
72
+ type SP = Paths<ST>;
73
+ type MP<P extends SP> = `${K}.${P}`;
74
+ type ScopedMainPaths = Paths<T> & MP<SP>;
58
75
 
59
76
  // Create reactive data scoped to subform path
60
- const data = getLens(mainForm.data, path) as Ref<ST>
77
+ const data = getLens(mainForm.data, path) as Ref<ST>;
61
78
 
62
79
  const initialData = computed(() => {
63
- return getNestedValue(mainForm.initialData.value, path) as ST
64
- })
80
+ return getNestedValue(mainForm.initialData.value, path) as ST;
81
+ });
65
82
 
66
83
  const adaptMainFormField = <S extends SP>(
67
84
  field: FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>,
@@ -69,92 +86,111 @@ export function createSubformInterface<
69
86
  // Where P ist the full path in the main form, we need to adapt it to the subform's path
70
87
  return {
71
88
  ...field,
72
- path: computed(() => unref(field.path).replace(path + '.', '')),
89
+ path: computed(() => unref(field.path).replace(path + ".", "")),
73
90
  setData: (newData: PickProps<ST, S>) => {
74
- field.setData(newData as PickProps<T, ScopedMainPaths>)
91
+ field.setData(newData as PickProps<T, ScopedMainPaths>);
75
92
  },
76
- } as unknown as FormField<PickProps<ST, S>, S>
77
- }
93
+ } as unknown as FormField<PickProps<ST, S>, S>;
94
+ };
78
95
 
79
96
  const getField = <P extends SP>(fieldPath: P) => {
80
- const fullPath = joinPath(path, fieldPath)
81
- const mainFormField = mainForm.getField(fullPath as ScopedMainPaths)
97
+ const fullPath = joinPath(path, fieldPath);
98
+ const mainFormField = mainForm.getField(fullPath as ScopedMainPaths);
82
99
 
83
100
  if (!mainFormField) {
84
- return {} as FormField<PickProps<ST, P>, P>
101
+ return {} as FormField<PickProps<ST, P>, P>;
85
102
  }
86
103
 
87
- return adaptMainFormField<P>(mainFormField)
88
- }
104
+ return adaptMainFormField<P>(mainFormField);
105
+ };
89
106
 
90
107
  // Field operations with path transformation
91
- const defineField = <P extends SP>(fieldOptions: DefineFieldOptions<ST, P>) => {
92
- const fullPath = joinPath(path, fieldOptions.path)
108
+ const defineField = <P extends SP>(
109
+ fieldOptions: DefineFieldOptions<ST, P>,
110
+ ) => {
111
+ const fullPath = joinPath(path, fieldOptions.path);
93
112
 
94
113
  const mainField = mainForm.defineField({
95
114
  ...fieldOptions,
96
115
  path: fullPath as ScopedMainPaths,
97
- })
116
+ });
98
117
 
99
- return adaptMainFormField<P>(mainField)
100
- }
118
+ return adaptMainFormField<P>(mainField);
119
+ };
101
120
 
102
121
  const fields = computed(<P extends SP>() => {
103
- return (mainForm.fields.value as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
122
+ return (
123
+ mainForm.fields.value as FormField<
124
+ PickProps<T, ScopedMainPaths>,
125
+ ScopedMainPaths
126
+ >[]
127
+ )
104
128
  .filter((field) => {
105
- const fieldPath = field.path.value
106
- return fieldPath.startsWith(path + '.') || fieldPath === path
129
+ const fieldPath = field.path.value;
130
+ return fieldPath.startsWith(path + ".") || fieldPath === path;
107
131
  })
108
- .map(field => adaptMainFormField(field)) as FieldsTuple<ST, P>
109
- })
132
+ .map((field) => adaptMainFormField(field)) as FieldsTuple<ST, P>;
133
+ });
110
134
 
111
135
  // Helper function to get all fields without type parameter
112
136
  const getAllSubformFields = () => {
113
- return (mainForm.fields.value as FormField<PickProps<T, ScopedMainPaths>, ScopedMainPaths>[])
114
- .filter((field) => {
115
- const fieldPath = field.path.value
116
- return fieldPath.startsWith(path + '.') || fieldPath === path
117
- })
118
- }
137
+ return (
138
+ mainForm.fields.value as FormField<
139
+ PickProps<T, ScopedMainPaths>,
140
+ ScopedMainPaths
141
+ >[]
142
+ ).filter((field) => {
143
+ const fieldPath = field.path.value;
144
+ return fieldPath.startsWith(path + ".") || fieldPath === path;
145
+ });
146
+ };
119
147
 
120
148
  // State computed from main form with path filtering
121
- const isDirty = computed(() => getAllSubformFields().some(field => field.dirty.value))
122
- const isTouched = computed(() => getAllSubformFields().some(field => field.touched.value))
149
+ const isDirty = computed(() =>
150
+ getAllSubformFields().some((field) => field.dirty.value),
151
+ );
152
+ const isTouched = computed(() =>
153
+ getAllSubformFields().some((field) => field.touched.value),
154
+ );
123
155
 
124
156
  // Validation delegates to main form
125
- const isValid = computed(() => mainForm.isValid.value)
126
- const isValidated = computed(() => mainForm.isValidated.value)
127
- const errors = computed(() => filterErrorsForPath(unref(mainForm.errors), path))
157
+ const isValid = computed(() => mainForm.isValid.value);
158
+ const isValidated = computed(() => mainForm.isValidated.value);
159
+ const errors = computed(() =>
160
+ filterErrorsForPath(unref(mainForm.errors), path),
161
+ );
128
162
 
129
- const validateForm = () => mainForm.validateForm()
163
+ const validateForm = () => mainForm.validateForm();
130
164
 
131
165
  // Nested subforms
132
166
  const getSubForm = <P extends EntityPaths<ST>>(
133
167
  subPath: P,
134
168
  subOptions?: SubformOptions<PickEntity<ST, P>>,
135
169
  ) => {
136
- const fullPath = joinPath(path, subPath) as EntityPaths<T>
170
+ const fullPath = joinPath(path, subPath) as EntityPaths<T>;
137
171
  return mainForm.getSubForm(
138
172
  fullPath,
139
173
  subOptions as SubformOptions<PickEntity<T, typeof fullPath>>,
140
- ) as Form<PickEntity<ST, P>>
141
- }
174
+ ) as Form<PickEntity<ST, P>>;
175
+ };
142
176
 
143
177
  // Reset scoped to this subform
144
- const reset = () => getAllSubformFields().forEach(field => field.reset())
178
+ const reset = () => getAllSubformFields().forEach((field) => field.reset());
145
179
 
146
- const defineValidator = (options: ValidatorOptions<ST> | Ref<Validator<ST>>) => {
147
- const subValidator = isRef(options) ? options : createValidator(options)
180
+ const defineValidator = (
181
+ options: ValidatorOptions<ST> | Ref<Validator<ST>>,
182
+ ) => {
183
+ const subValidator = isRef(options) ? options : createValidator(options);
148
184
  const validator = computed(
149
185
  () => new NestedValidator<T, K>(path, unref(subValidator)),
150
- )
186
+ );
151
187
 
152
- mainForm.defineValidator(validator)
188
+ mainForm.defineValidator(validator);
153
189
 
154
- return subValidator
155
- }
190
+ return subValidator;
191
+ };
156
192
 
157
- return {
193
+ const subFormInterface: Omit<Form<ST>, "submitHandler"> = {
158
194
  data: data,
159
195
  fields,
160
196
  initialData,
@@ -169,5 +205,12 @@ export function createSubformInterface<
169
205
  reset,
170
206
  validateForm,
171
207
  getSubForm,
172
- }
208
+ };
209
+
210
+ const submitHandler = useSubmitHandler(subFormInterface, formOptions ?? {});
211
+
212
+ return {
213
+ ...subFormInterface,
214
+ submitHandler,
215
+ };
173
216
  }
@@ -0,0 +1,29 @@
1
+ import type { Awaitable, MaybeRef } from '@vueuse/core'
2
+ import { unref } from 'vue'
3
+ import type { Form, FormDataDefault } from '../types/form'
4
+ import type { ValidationStrategy } from '../types/validation'
5
+
6
+ interface SubmitHandlerOptions {
7
+ validationStrategy?: MaybeRef<ValidationStrategy>
8
+ }
9
+
10
+ export function useSubmitHandler<T extends FormDataDefault>(
11
+ form: Omit<Form<T>, 'submitHandler'>,
12
+ options: SubmitHandlerOptions,
13
+ ) {
14
+ return (onSubmit: (data: T) => Awaitable<void>) => {
15
+ return async (event: SubmitEvent) => {
16
+ event.preventDefault()
17
+
18
+ if (unref(options.validationStrategy) !== 'none') {
19
+ await form.validateForm()
20
+ }
21
+
22
+ if (!form.isValid.value) {
23
+ return
24
+ }
25
+
26
+ await onSubmit(form.data.value)
27
+ }
28
+ }
29
+ }
package/src/types/form.ts CHANGED
@@ -80,5 +80,5 @@ export interface Form<T extends FormDataDefault> {
80
80
  getSubForm: <P extends EntityPaths<T>>(
81
81
  path: P,
82
82
  options?: SubformOptions<PickEntity<T, P>>,
83
- ) => Omit<Form<PickEntity<T, P>>, 'submitHandler'>
83
+ ) => Form<PickEntity<T, P>>
84
84
  }