@teamnovu/kit-vue-forms 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composables/useForm.d.ts +25 -4
- package/dist/composables/useSubform.d.ts +2 -1
- package/dist/composables/useSubmitHandler.d.ts +8 -0
- package/dist/index.js +167 -154
- package/package.json +1 -1
- package/src/composables/useForm.ts +44 -55
- package/src/composables/useSubform.ts +113 -70
- package/src/composables/useSubmitHandler.ts +29 -0
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
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>):
|
|
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) => Omit<Form<PickEntity<T, P>>, "submitHandler">;
|
|
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>>):
|
|
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
|
|
2
|
-
var
|
|
3
|
-
var S = (t, e, r) =>
|
|
4
|
-
import { toValue as
|
|
5
|
-
import { cloneDeep as
|
|
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 =
|
|
9
|
-
return
|
|
8
|
+
const e = Y(t), r = ee(e);
|
|
9
|
+
return de(r);
|
|
10
10
|
}
|
|
11
|
-
function
|
|
11
|
+
function W(t) {
|
|
12
12
|
return t === "" ? [] : t.split(/\s*\.\s*/).filter(Boolean);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
const r = Array.isArray(e) ? 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 :
|
|
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
|
|
25
|
-
const a = Array.isArray(e) ? e :
|
|
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,
|
|
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 (!
|
|
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
|
-
|
|
44
|
+
he(t, d(e), r);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
function
|
|
47
|
+
function b(t, e) {
|
|
48
48
|
return !t && !e ? "" : !t && e ? e : !e && t ? t : `${t}.${e}`;
|
|
49
49
|
}
|
|
50
|
-
function
|
|
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
|
|
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
|
|
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
|
-
},
|
|
99
|
+
}, p = () => {
|
|
100
100
|
var h;
|
|
101
101
|
s.touched = !0, s.errors = [], (h = r.onBlur) == null || h.call(r);
|
|
102
|
-
},
|
|
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:
|
|
125
|
-
onFocus:
|
|
124
|
+
onBlur: p,
|
|
125
|
+
onFocus: F,
|
|
126
126
|
reset: V,
|
|
127
127
|
setErrors: c,
|
|
128
128
|
clearErrors: o
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
-
const
|
|
131
|
+
const Ve = {
|
|
132
132
|
keepValuesOnUnmount: !0
|
|
133
133
|
};
|
|
134
|
-
function
|
|
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(),
|
|
139
|
+
a.value = r(), ae(a);
|
|
140
140
|
},
|
|
141
141
|
{ flush: "sync" }
|
|
142
142
|
), a;
|
|
143
143
|
}
|
|
144
|
-
function
|
|
145
|
-
const a = /* @__PURE__ */ new Map(), s =
|
|
146
|
-
...
|
|
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
|
-
},
|
|
151
|
+
}, p = (o) => {
|
|
152
152
|
var i;
|
|
153
153
|
n != null && n.keepValuesOnUnmount || (i = s.get(o)) == null || i.reset(), s.delete(o);
|
|
154
|
-
},
|
|
154
|
+
}, F = (o) => {
|
|
155
155
|
var i;
|
|
156
|
-
a.has(o) ? (i = a.get(o)) == null || i.inc() : a.set(o, new
|
|
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
|
|
163
|
+
const A = pe({
|
|
164
164
|
path: i,
|
|
165
|
-
value: L(
|
|
166
|
-
initialValue:
|
|
167
|
-
existsInForm: v(() =>
|
|
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(
|
|
191
|
+
f(A);
|
|
192
192
|
}
|
|
193
193
|
const h = s.get(i);
|
|
194
|
-
return
|
|
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:
|
|
202
|
+
deregisterField: p,
|
|
203
203
|
defineField: c
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
|
-
function
|
|
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
|
|
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
|
|
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
|
|
233
|
+
return Ee(s);
|
|
229
234
|
}, t[0]);
|
|
230
235
|
}
|
|
231
|
-
function
|
|
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]:
|
|
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:
|
|
250
|
-
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
|
|
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
|
|
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
|
|
278
|
+
const y = {
|
|
274
279
|
isValid: !0,
|
|
275
280
|
errors: {
|
|
276
281
|
general: [],
|
|
277
282
|
propertyErrors: {}
|
|
278
283
|
}
|
|
279
284
|
};
|
|
280
|
-
class
|
|
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
|
|
291
|
+
return y;
|
|
287
292
|
const r = await this.schema.safeParseAsync(e);
|
|
288
293
|
if (r.success)
|
|
289
|
-
return
|
|
290
|
-
const a =
|
|
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
|
|
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
|
|
311
|
+
return y;
|
|
307
312
|
try {
|
|
308
313
|
const r = await this.validateFn(e);
|
|
309
|
-
return r.isValid ?
|
|
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
|
|
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
|
|
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
|
|
339
|
-
return v(() => new
|
|
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
|
|
349
|
+
function be(t, e) {
|
|
345
350
|
const r = _({
|
|
346
|
-
validators: J([
|
|
351
|
+
validators: J([U(e)]),
|
|
347
352
|
isValidated: !1,
|
|
348
|
-
errors: d(e.errors) ??
|
|
349
|
-
}), a = (l =
|
|
350
|
-
r.errors = C(d(e.errors) ??
|
|
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 =
|
|
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 =
|
|
374
|
-
return r.validators.push(c),
|
|
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 } =
|
|
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: !
|
|
402
|
+
isValid: !O(l.errors),
|
|
398
403
|
errors: r.errors
|
|
399
404
|
};
|
|
400
|
-
},
|
|
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: !
|
|
413
|
+
isValid: !O(c.errors),
|
|
409
414
|
errors: r.errors
|
|
410
415
|
};
|
|
411
|
-
},
|
|
412
|
-
r.isValidated = !1, r.errors = d(e.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:
|
|
422
|
+
validateField: p,
|
|
418
423
|
defineValidator: s,
|
|
419
|
-
isValid:
|
|
424
|
+
isValid: F,
|
|
420
425
|
reset: V
|
|
421
426
|
};
|
|
422
427
|
}
|
|
423
|
-
class
|
|
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
|
|
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(
|
|
438
|
-
|
|
439
|
-
|
|
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
|
|
447
|
-
const
|
|
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: (
|
|
451
|
-
u.setData(
|
|
454
|
+
setData: (m) => {
|
|
455
|
+
u.setData(m);
|
|
452
456
|
}
|
|
453
|
-
}),
|
|
454
|
-
const
|
|
455
|
-
return w ?
|
|
456
|
-
},
|
|
457
|
-
const
|
|
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:
|
|
463
|
+
path: m
|
|
460
464
|
});
|
|
461
|
-
return
|
|
462
|
-
},
|
|
463
|
-
const
|
|
464
|
-
return
|
|
465
|
-
}).map((u) =>
|
|
466
|
-
const
|
|
467
|
-
return
|
|
468
|
-
}),
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
|
482
|
-
() => new
|
|
490
|
+
const m = k(u) ? u : U(u), w = v(
|
|
491
|
+
() => new $e(e, d(m))
|
|
483
492
|
);
|
|
484
|
-
return t.defineValidator(w),
|
|
493
|
+
return t.defineValidator(w), m;
|
|
485
494
|
},
|
|
486
|
-
reset: () =>
|
|
495
|
+
reset: () => l().forEach((u) => u.reset()),
|
|
487
496
|
validateForm: () => t.validateForm(),
|
|
488
|
-
getSubForm: (u,
|
|
489
|
-
const w =
|
|
497
|
+
getSubForm: (u, m) => {
|
|
498
|
+
const w = b(e, u);
|
|
490
499
|
return t.getSubForm(
|
|
491
500
|
w,
|
|
492
|
-
|
|
501
|
+
m
|
|
493
502
|
);
|
|
494
503
|
}
|
|
504
|
+
}, q = G(z, r ?? {});
|
|
505
|
+
return {
|
|
506
|
+
...z,
|
|
507
|
+
submitHandler: q
|
|
495
508
|
};
|
|
496
509
|
}
|
|
497
|
-
function
|
|
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 =
|
|
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 =
|
|
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
|
|
522
|
-
return
|
|
532
|
+
function F(c, o) {
|
|
533
|
+
return _e(V, c, t);
|
|
523
534
|
}
|
|
524
|
-
const
|
|
535
|
+
const V = {
|
|
525
536
|
...n,
|
|
526
537
|
...s,
|
|
527
538
|
...f,
|
|
528
|
-
reset:
|
|
529
|
-
getSubForm:
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
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) =>
|
|
565
|
+
return (s, n) => $(s.$slots, "default", M(N(a)));
|
|
553
566
|
}
|
|
554
|
-
}),
|
|
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 =
|
|
566
|
-
return K(),
|
|
578
|
+
const a = ie("Field");
|
|
579
|
+
return K(), H(a, {
|
|
567
580
|
form: t.form,
|
|
568
581
|
path: t.path
|
|
569
582
|
}, {
|
|
570
|
-
default:
|
|
571
|
-
(K(),
|
|
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
|
-
}),
|
|
577
|
-
default:
|
|
578
|
-
|
|
589
|
+
}), ce({
|
|
590
|
+
default: B(() => [
|
|
591
|
+
$(e.$slots, "default")
|
|
579
592
|
]),
|
|
580
593
|
_: 2
|
|
581
594
|
}, [
|
|
582
|
-
|
|
583
|
-
name:
|
|
584
|
-
fn:
|
|
585
|
-
|
|
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
|
-
}),
|
|
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) =>
|
|
615
|
+
return (a, s) => $(a.$slots, "default", M(N({ subform: r.value })));
|
|
603
616
|
}
|
|
604
617
|
});
|
|
605
618
|
export {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
619
|
+
ke as Field,
|
|
620
|
+
xe as FormFieldWrapper,
|
|
621
|
+
Me as FormPart,
|
|
622
|
+
Ue as useForm
|
|
610
623
|
};
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Awaitable } from
|
|
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
|
|
13
|
-
import type { Form, FormDataDefault } from
|
|
14
|
-
import type { EntityPaths, PickEntity } from
|
|
15
|
-
import type { ValidationStrategy } from
|
|
16
|
-
import { cloneRefValue } from
|
|
17
|
-
import { useFieldRegistry } from
|
|
18
|
-
import { useFormState } from
|
|
19
|
-
import { createSubformInterface, type SubformOptions } from
|
|
20
|
-
import { useValidation, type ValidationOptions } from
|
|
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:
|
|
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) ===
|
|
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
|
-
|
|
85
|
-
): Omit<Form<PickEntity<T, K>>,
|
|
86
|
-
return createSubformInterface(formInterface, path, options)
|
|
69
|
+
subformOptions?: SubformOptions<PickEntity<T, K>>,
|
|
70
|
+
): Omit<Form<PickEntity<T, K>>, "submitHandler"> {
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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) ===
|
|
101
|
-
validationState.validateForm()
|
|
86
|
+
if (unref(options.validationStrategy) === "onFormOpen") {
|
|
87
|
+
validationState.validateForm();
|
|
102
88
|
}
|
|
103
89
|
|
|
104
|
-
return
|
|
90
|
+
return {
|
|
91
|
+
...formInterface,
|
|
92
|
+
submitHandler,
|
|
93
|
+
};
|
|
105
94
|
}
|
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
import { computed, isRef, unref, type Ref } from
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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>>
|
|
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(
|
|
36
|
-
joinPath(this.path, key),
|
|
37
|
-
|
|
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
|
-
):
|
|
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>(
|
|
92
|
-
|
|
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 (
|
|
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 +
|
|
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 (
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
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(() =>
|
|
122
|
-
|
|
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(() =>
|
|
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 = (
|
|
147
|
-
|
|
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
|
-
|
|
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
|
+
}
|