@teamnovu/kit-vue-forms 0.1.16 → 0.1.17
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/components/FormPart.vue.d.ts +1 -1
- package/dist/composables/useFieldRegistry.d.ts +2 -2
- package/dist/composables/useSubform.d.ts +1 -1
- package/dist/index.js +153 -158
- package/dist/types/form.d.ts +1 -1
- package/dist/utils/path.d.ts +1 -1
- package/package.json +2 -2
- package/src/composables/useFieldRegistry.ts +74 -74
- package/src/composables/useForm.ts +43 -48
- package/src/composables/useSubform.ts +1 -1
- package/src/types/form.ts +1 -1
- package/src/utils/path.ts +41 -42
|
@@ -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: Form< PickEntity<TData, TPath
|
|
14
|
+
subform: Omit<Form< PickEntity<TData, TPath>>, "submitHandler">;
|
|
15
15
|
}): any;
|
|
16
16
|
};
|
|
17
17
|
emit: {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { Awaitable } from '@vueuse/core';
|
|
1
2
|
import { MaybeRef, ComputedRef } from 'vue';
|
|
2
3
|
import { FieldsTuple, FormDataDefault, FormField } from '../types/form';
|
|
3
4
|
import { Paths, PickProps } from '../types/util';
|
|
4
5
|
import { UseFieldOptions } from './useField';
|
|
5
6
|
import { ValidationState } from './useValidation';
|
|
6
|
-
import { Awaitable } from '@vueuse/core';
|
|
7
7
|
export type ResolvedFormField<T, K extends Paths<T>> = FormField<PickProps<T, K>, K>;
|
|
8
|
-
export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>,
|
|
8
|
+
export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>, "path"> & {
|
|
9
9
|
onBlur?: () => void;
|
|
10
10
|
onFocus?: () => void;
|
|
11
11
|
};
|
|
@@ -2,4 +2,4 @@ import { Form, FormDataDefault } from '../types/form';
|
|
|
2
2
|
import { EntityPaths, PickEntity } from '../types/util';
|
|
3
3
|
export interface SubformOptions<_T extends FormDataDefault> {
|
|
4
4
|
}
|
|
5
|
-
export declare function createSubformInterface<T extends FormDataDefault, K extends EntityPaths<T>>(mainForm: Form<T>, path: K, _options?: SubformOptions<PickEntity<T, K>>): Form<PickEntity<T, K
|
|
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'>;
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
var H = Object.defineProperty;
|
|
2
2
|
var Z = (t, e, r) => e in t ? H(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
|
|
3
3
|
var S = (t, e, r) => Z(t, typeof e != "symbol" ? e + "" : e, r);
|
|
4
|
-
import { toValue as q, toRaw as Q, computed as
|
|
5
|
-
import { cloneDeep as
|
|
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";
|
|
6
6
|
import "zod";
|
|
7
7
|
function g(t) {
|
|
8
8
|
const e = q(t), r = Q(e);
|
|
9
|
-
return
|
|
9
|
+
return le(r);
|
|
10
10
|
}
|
|
11
|
-
function
|
|
11
|
+
function N(t) {
|
|
12
12
|
return t === "" ? [] : t.split(/\s*\.\s*/).filter(Boolean);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
const r = Array.isArray(e) ? e :
|
|
14
|
+
function ce(t, e) {
|
|
15
|
+
const r = Array.isArray(e) ? e : N(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 : N(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 ue(t, e, r) {
|
|
25
|
+
const a = Array.isArray(e) ? e : N(e), s = a.at(-1);
|
|
26
26
|
if (s) {
|
|
27
27
|
const n = a.slice(0, -1).reduce(
|
|
28
28
|
(f, m) => ((f == null ? void 0 : f[m]) === void 0 && (f[m] = {}), f == null ? void 0 : f[m]),
|
|
@@ -36,18 +36,18 @@ function de(t, e, r) {
|
|
|
36
36
|
t.value = r;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
const
|
|
39
|
+
const L = (t, e) => v({
|
|
40
40
|
get() {
|
|
41
41
|
return D(d(t), d(e));
|
|
42
42
|
},
|
|
43
43
|
set(r) {
|
|
44
|
-
|
|
44
|
+
ue(t, d(e), r);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
function
|
|
47
|
+
function $(t, e) {
|
|
48
48
|
return !t && !e ? "" : !t && e ? e : !e && t ? t : `${t}.${e}`;
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function de(t, e) {
|
|
51
51
|
if (!e)
|
|
52
52
|
return t;
|
|
53
53
|
const r = `${e}.`, a = Object.fromEntries(
|
|
@@ -62,7 +62,7 @@ function fe(t, e) {
|
|
|
62
62
|
propertyErrors: a
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
class
|
|
65
|
+
class fe {
|
|
66
66
|
constructor(e) {
|
|
67
67
|
S(this, "rc", 1);
|
|
68
68
|
this.drop = e;
|
|
@@ -74,13 +74,13 @@ class he {
|
|
|
74
74
|
this.rc > 0 && (this.rc -= 1, this.rc === 0 && this.drop && this.drop());
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
function
|
|
77
|
+
function he(t) {
|
|
78
78
|
const r = {
|
|
79
79
|
...{
|
|
80
80
|
existsInForm: !0
|
|
81
81
|
},
|
|
82
82
|
...t
|
|
83
|
-
}, a =
|
|
83
|
+
}, a = j(Object.freeze(g(r.initialValue))), s = _({
|
|
84
84
|
value: r.value,
|
|
85
85
|
path: r.path,
|
|
86
86
|
initialValue: a,
|
|
@@ -88,13 +88,13 @@ function pe(t) {
|
|
|
88
88
|
touched: !1
|
|
89
89
|
});
|
|
90
90
|
P(
|
|
91
|
-
|
|
91
|
+
j(r.initialValue),
|
|
92
92
|
() => {
|
|
93
93
|
a.value = Object.freeze(g(r.initialValue)), s.value = g(r.initialValue);
|
|
94
94
|
},
|
|
95
95
|
{ flush: "sync" }
|
|
96
96
|
);
|
|
97
|
-
const n =
|
|
97
|
+
const n = v(() => JSON.stringify(s.value) !== JSON.stringify(s.initialValue)), f = (h) => {
|
|
98
98
|
s.value = h;
|
|
99
99
|
}, m = () => {
|
|
100
100
|
var h;
|
|
@@ -109,15 +109,15 @@ function pe(t) {
|
|
|
109
109
|
n.value || f(g(h)), s.initialValue = h;
|
|
110
110
|
}, c = (h) => {
|
|
111
111
|
s.errors = h;
|
|
112
|
-
},
|
|
112
|
+
}, o = () => {
|
|
113
113
|
s.errors = [];
|
|
114
|
-
},
|
|
114
|
+
}, i = T(s);
|
|
115
115
|
return {
|
|
116
|
-
data:
|
|
117
|
-
path:
|
|
118
|
-
initialValue:
|
|
119
|
-
errors:
|
|
120
|
-
touched:
|
|
116
|
+
data: i.value,
|
|
117
|
+
path: i.path,
|
|
118
|
+
initialValue: i.initialValue,
|
|
119
|
+
errors: i.errors,
|
|
120
|
+
touched: i.touched,
|
|
121
121
|
dirty: n,
|
|
122
122
|
setData: f,
|
|
123
123
|
setInitialData: l,
|
|
@@ -125,95 +125,92 @@ function pe(t) {
|
|
|
125
125
|
onFocus: y,
|
|
126
126
|
reset: V,
|
|
127
127
|
setErrors: c,
|
|
128
|
-
clearErrors:
|
|
128
|
+
clearErrors: o
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
const ve = {
|
|
132
132
|
keepValuesOnUnmount: !0
|
|
133
133
|
};
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
return
|
|
134
|
+
function pe(t, e) {
|
|
135
|
+
const r = () => D(t.initialData, e), a = j(r());
|
|
136
|
+
return P(
|
|
137
|
+
() => t.initialData,
|
|
137
138
|
() => {
|
|
138
|
-
|
|
139
|
+
a.value = r(), ee(a);
|
|
139
140
|
},
|
|
140
141
|
{ flush: "sync" }
|
|
141
|
-
),
|
|
142
|
+
), a;
|
|
142
143
|
}
|
|
143
|
-
function
|
|
144
|
+
function me(t, e, r) {
|
|
144
145
|
const a = /* @__PURE__ */ new Map(), s = X(/* @__PURE__ */ new Map()), n = {
|
|
145
146
|
...ve,
|
|
146
147
|
...r
|
|
147
|
-
}, f = (
|
|
148
|
-
const
|
|
149
|
-
s.set(
|
|
150
|
-
}, m = (
|
|
151
|
-
var
|
|
152
|
-
n != null && n.keepValuesOnUnmount || (
|
|
153
|
-
}, y = (
|
|
154
|
-
var
|
|
155
|
-
a.has(
|
|
156
|
-
}, V = (
|
|
157
|
-
var
|
|
158
|
-
a.has(
|
|
159
|
-
}, l = (
|
|
160
|
-
const { path:
|
|
161
|
-
if (!s.has(
|
|
162
|
-
const
|
|
163
|
-
path:
|
|
164
|
-
value:
|
|
165
|
-
initialValue:
|
|
166
|
-
existsInForm:
|
|
167
|
-
errors:
|
|
148
|
+
}, f = (o) => {
|
|
149
|
+
const i = d(o.path);
|
|
150
|
+
s.set(i, o);
|
|
151
|
+
}, m = (o) => {
|
|
152
|
+
var i;
|
|
153
|
+
n != null && n.keepValuesOnUnmount || (i = s.get(o)) == null || i.reset(), s.delete(o);
|
|
154
|
+
}, y = (o) => {
|
|
155
|
+
var i;
|
|
156
|
+
a.has(o) ? (i = a.get(o)) == null || i.inc() : a.set(o, new fe(() => m(o)));
|
|
157
|
+
}, V = (o) => {
|
|
158
|
+
var i;
|
|
159
|
+
a.has(o) && ((i = a.get(o)) == null || i.dec());
|
|
160
|
+
}, l = (o) => {
|
|
161
|
+
const { path: i } = o;
|
|
162
|
+
if (!s.has(i)) {
|
|
163
|
+
const W = he({
|
|
164
|
+
path: i,
|
|
165
|
+
value: L(O(t, "data"), i),
|
|
166
|
+
initialValue: pe(t, i),
|
|
167
|
+
existsInForm: v(() => ce(t.data, d(i))),
|
|
168
|
+
errors: v({
|
|
168
169
|
get() {
|
|
169
|
-
return e.errors.value.propertyErrors[
|
|
170
|
+
return e.errors.value.propertyErrors[i] || [];
|
|
170
171
|
},
|
|
171
172
|
set(E) {
|
|
172
|
-
e.errors.value.propertyErrors[
|
|
173
|
+
e.errors.value.propertyErrors[i] = E;
|
|
173
174
|
}
|
|
174
175
|
}),
|
|
175
176
|
onBlur: async () => {
|
|
176
177
|
var E, R;
|
|
177
|
-
await Promise.all(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
]
|
|
182
|
-
);
|
|
178
|
+
await Promise.all([
|
|
179
|
+
(E = n == null ? void 0 : n.onBlur) == null ? void 0 : E.call(n, d(i)),
|
|
180
|
+
(R = o.onBlur) == null ? void 0 : R.call(o)
|
|
181
|
+
]);
|
|
183
182
|
},
|
|
184
183
|
onFocus: async () => {
|
|
185
184
|
var E, R;
|
|
186
|
-
await Promise.all(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
]
|
|
191
|
-
);
|
|
185
|
+
await Promise.all([
|
|
186
|
+
(E = n == null ? void 0 : n.onFocus) == null ? void 0 : E.call(n, d(i)),
|
|
187
|
+
(R = o.onFocus) == null ? void 0 : R.call(o)
|
|
188
|
+
]);
|
|
192
189
|
}
|
|
193
190
|
});
|
|
194
|
-
f(
|
|
191
|
+
f(W);
|
|
195
192
|
}
|
|
196
|
-
const h = s.get(
|
|
197
|
-
return y(
|
|
198
|
-
V(
|
|
193
|
+
const h = s.get(i);
|
|
194
|
+
return y(i), Y(() => {
|
|
195
|
+
V(i);
|
|
199
196
|
}), h;
|
|
200
|
-
}, c = (
|
|
197
|
+
}, c = (o) => l(o);
|
|
201
198
|
return {
|
|
202
|
-
fields:
|
|
203
|
-
getField: (
|
|
199
|
+
fields: v(() => [...s.values()]),
|
|
200
|
+
getField: (o) => l({ path: o }),
|
|
204
201
|
registerField: f,
|
|
205
202
|
deregisterField: m,
|
|
206
203
|
defineField: c
|
|
207
204
|
};
|
|
208
205
|
}
|
|
209
|
-
function
|
|
210
|
-
const e =
|
|
206
|
+
function Ve(t) {
|
|
207
|
+
const e = v(() => t.fields.value.some((a) => d(a.dirty))), r = v(() => t.fields.value.some((a) => d(a.touched)));
|
|
211
208
|
return {
|
|
212
209
|
isDirty: e,
|
|
213
210
|
isTouched: r
|
|
214
211
|
};
|
|
215
212
|
}
|
|
216
|
-
function
|
|
213
|
+
function ye(t) {
|
|
217
214
|
return t.filter(
|
|
218
215
|
(e, r, a) => a.indexOf(e) === r
|
|
219
216
|
);
|
|
@@ -228,10 +225,10 @@ function G(...t) {
|
|
|
228
225
|
if (!a)
|
|
229
226
|
return e;
|
|
230
227
|
const s = (e ?? []).concat(r);
|
|
231
|
-
return
|
|
228
|
+
return ye(s);
|
|
232
229
|
}, t[0]);
|
|
233
230
|
}
|
|
234
|
-
function
|
|
231
|
+
function Fe(...t) {
|
|
235
232
|
return t.map((r) => Object.keys(r)).flat().reduce((r, a) => {
|
|
236
233
|
const s = t.map((n) => n[a]).filter(Boolean);
|
|
237
234
|
return {
|
|
@@ -250,17 +247,17 @@ function C(...t) {
|
|
|
250
247
|
return t.length === 1 ? e : t.slice(1).reduce(
|
|
251
248
|
(r, a) => ({
|
|
252
249
|
general: G(r.general, a.general),
|
|
253
|
-
propertyErrors:
|
|
250
|
+
propertyErrors: Fe(r.propertyErrors ?? {}, a.propertyErrors ?? {})
|
|
254
251
|
}),
|
|
255
252
|
e
|
|
256
253
|
);
|
|
257
254
|
}
|
|
258
|
-
function
|
|
255
|
+
function B(t) {
|
|
259
256
|
var a;
|
|
260
257
|
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;
|
|
261
258
|
return e || r;
|
|
262
259
|
}
|
|
263
|
-
function
|
|
260
|
+
function ge(t) {
|
|
264
261
|
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) => {
|
|
265
262
|
const n = s.path.join(".");
|
|
266
263
|
return {
|
|
@@ -280,7 +277,7 @@ const F = {
|
|
|
280
277
|
propertyErrors: {}
|
|
281
278
|
}
|
|
282
279
|
};
|
|
283
|
-
class
|
|
280
|
+
class Ee {
|
|
284
281
|
constructor(e) {
|
|
285
282
|
this.schema = e;
|
|
286
283
|
}
|
|
@@ -290,7 +287,7 @@ class we {
|
|
|
290
287
|
const r = await this.schema.safeParseAsync(e);
|
|
291
288
|
if (r.success)
|
|
292
289
|
return F;
|
|
293
|
-
const a =
|
|
290
|
+
const a = ge(r.error);
|
|
294
291
|
return {
|
|
295
292
|
isValid: !1,
|
|
296
293
|
errors: {
|
|
@@ -300,7 +297,7 @@ class we {
|
|
|
300
297
|
};
|
|
301
298
|
}
|
|
302
299
|
}
|
|
303
|
-
class
|
|
300
|
+
class we {
|
|
304
301
|
constructor(e) {
|
|
305
302
|
this.validateFn = e;
|
|
306
303
|
}
|
|
@@ -321,11 +318,11 @@ class Re {
|
|
|
321
318
|
}
|
|
322
319
|
}
|
|
323
320
|
}
|
|
324
|
-
class
|
|
321
|
+
class Re {
|
|
325
322
|
constructor(e, r) {
|
|
326
323
|
S(this, "schemaValidator");
|
|
327
324
|
S(this, "functionValidator");
|
|
328
|
-
this.schema = e, this.validateFn = r, this.schemaValidator = new
|
|
325
|
+
this.schema = e, this.validateFn = r, this.schemaValidator = new Ee(this.schema), this.functionValidator = new we(this.validateFn);
|
|
329
326
|
}
|
|
330
327
|
async validate(e) {
|
|
331
328
|
const [r, a] = await Promise.all([
|
|
@@ -339,14 +336,14 @@ class Pe {
|
|
|
339
336
|
}
|
|
340
337
|
}
|
|
341
338
|
function I(t) {
|
|
342
|
-
return
|
|
339
|
+
return v(() => new Re(
|
|
343
340
|
d(t.schema),
|
|
344
341
|
d(t.validateFn)
|
|
345
342
|
));
|
|
346
343
|
}
|
|
347
|
-
function
|
|
348
|
-
const r =
|
|
349
|
-
validators:
|
|
344
|
+
function Pe(t, e) {
|
|
345
|
+
const r = _({
|
|
346
|
+
validators: J([I(e)]),
|
|
350
347
|
isValidated: !1,
|
|
351
348
|
errors: d(e.errors) ?? F.errors
|
|
352
349
|
}), a = (l = F.errors) => {
|
|
@@ -374,30 +371,30 @@ function De(t, e) {
|
|
|
374
371
|
});
|
|
375
372
|
const s = (l) => {
|
|
376
373
|
const c = U(l) ? l : I(l);
|
|
377
|
-
return r.validators.push(c),
|
|
374
|
+
return r.validators.push(c), re() && te(() => {
|
|
378
375
|
r.validators = r.validators.filter(
|
|
379
|
-
(
|
|
376
|
+
(o) => o !== c
|
|
380
377
|
);
|
|
381
378
|
}), c;
|
|
382
379
|
};
|
|
383
380
|
async function n() {
|
|
384
381
|
const l = await Promise.all(
|
|
385
|
-
r.validators.filter((
|
|
386
|
-
), c = l.every((
|
|
387
|
-
let { errors:
|
|
382
|
+
r.validators.filter((i) => d(i) !== void 0).map((i) => d(i).validate(t.data))
|
|
383
|
+
), c = l.every((i) => i.isValid);
|
|
384
|
+
let { errors: o } = F;
|
|
388
385
|
if (!c) {
|
|
389
|
-
const
|
|
390
|
-
|
|
386
|
+
const i = l.map((h) => h.errors);
|
|
387
|
+
o = C(...i);
|
|
391
388
|
}
|
|
392
389
|
return {
|
|
393
|
-
errors:
|
|
390
|
+
errors: o,
|
|
394
391
|
isValid: c
|
|
395
392
|
};
|
|
396
393
|
}
|
|
397
394
|
const f = async () => {
|
|
398
395
|
const l = await n();
|
|
399
396
|
return a(l.errors), r.isValidated = !0, {
|
|
400
|
-
isValid: !
|
|
397
|
+
isValid: !B(l.errors),
|
|
401
398
|
errors: r.errors
|
|
402
399
|
};
|
|
403
400
|
}, m = async (l) => {
|
|
@@ -408,14 +405,14 @@ function De(t, e) {
|
|
|
408
405
|
[l]: c.errors.propertyErrors[l]
|
|
409
406
|
}
|
|
410
407
|
}), {
|
|
411
|
-
isValid: !
|
|
408
|
+
isValid: !B(c.errors),
|
|
412
409
|
errors: r.errors
|
|
413
410
|
};
|
|
414
|
-
}, y =
|
|
411
|
+
}, y = v(() => !B(r.errors)), V = () => {
|
|
415
412
|
r.isValidated = !1, r.errors = d(e.errors) ?? F.errors;
|
|
416
413
|
};
|
|
417
414
|
return {
|
|
418
|
-
...
|
|
415
|
+
...T(r),
|
|
419
416
|
validateForm: f,
|
|
420
417
|
validateField: m,
|
|
421
418
|
defineValidator: s,
|
|
@@ -423,7 +420,7 @@ function De(t, e) {
|
|
|
423
420
|
reset: V
|
|
424
421
|
};
|
|
425
422
|
}
|
|
426
|
-
class
|
|
423
|
+
class De {
|
|
427
424
|
constructor(e, r) {
|
|
428
425
|
this.path = e, this.validator = r;
|
|
429
426
|
}
|
|
@@ -438,7 +435,7 @@ class Se {
|
|
|
438
435
|
general: a.errors.general || [],
|
|
439
436
|
propertyErrors: a.errors.propertyErrors ? Object.fromEntries(
|
|
440
437
|
Object.entries(a.errors.propertyErrors).map(([s, n]) => [
|
|
441
|
-
|
|
438
|
+
$(this.path, s),
|
|
442
439
|
n
|
|
443
440
|
])
|
|
444
441
|
) : {}
|
|
@@ -446,29 +443,29 @@ class Se {
|
|
|
446
443
|
};
|
|
447
444
|
}
|
|
448
445
|
}
|
|
449
|
-
function
|
|
450
|
-
const a =
|
|
446
|
+
function Se(t, e, r) {
|
|
447
|
+
const a = L(t.data, e), s = v(() => D(t.initialData.value, e)), n = (u) => ({
|
|
451
448
|
...u,
|
|
452
|
-
path:
|
|
453
|
-
setData: (
|
|
454
|
-
u.setData(
|
|
449
|
+
path: v(() => d(u.path).replace(e + ".", "")),
|
|
450
|
+
setData: (p) => {
|
|
451
|
+
u.setData(p);
|
|
455
452
|
}
|
|
456
453
|
}), f = (u) => {
|
|
457
|
-
const
|
|
454
|
+
const p = $(e, u), w = t.getField(p);
|
|
458
455
|
return w ? n(w) : {};
|
|
459
456
|
}, m = (u) => {
|
|
460
|
-
const
|
|
457
|
+
const p = $(e, u.path), w = t.defineField({
|
|
461
458
|
...u,
|
|
462
|
-
path:
|
|
459
|
+
path: p
|
|
463
460
|
});
|
|
464
461
|
return n(w);
|
|
465
|
-
}, y =
|
|
466
|
-
const
|
|
467
|
-
return
|
|
462
|
+
}, y = v(() => t.fields.value.filter((u) => {
|
|
463
|
+
const p = u.path.value;
|
|
464
|
+
return p.startsWith(e + ".") || p === e;
|
|
468
465
|
}).map((u) => n(u))), V = () => t.fields.value.filter((u) => {
|
|
469
|
-
const
|
|
470
|
-
return
|
|
471
|
-
}), l =
|
|
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));
|
|
472
469
|
return {
|
|
473
470
|
data: a,
|
|
474
471
|
fields: y,
|
|
@@ -477,30 +474,28 @@ function _e(t, e, r) {
|
|
|
477
474
|
getField: f,
|
|
478
475
|
isDirty: l,
|
|
479
476
|
isTouched: c,
|
|
480
|
-
isValid:
|
|
481
|
-
isValidated:
|
|
477
|
+
isValid: o,
|
|
478
|
+
isValidated: i,
|
|
482
479
|
errors: h,
|
|
483
480
|
defineValidator: (u) => {
|
|
484
|
-
const
|
|
485
|
-
() => new
|
|
481
|
+
const p = U(u) ? u : I(u), w = v(
|
|
482
|
+
() => new De(e, d(p))
|
|
486
483
|
);
|
|
487
|
-
return t.defineValidator(w),
|
|
484
|
+
return t.defineValidator(w), p;
|
|
488
485
|
},
|
|
489
486
|
reset: () => V().forEach((u) => u.reset()),
|
|
490
487
|
validateForm: () => t.validateForm(),
|
|
491
|
-
getSubForm: (u,
|
|
492
|
-
const w =
|
|
488
|
+
getSubForm: (u, p) => {
|
|
489
|
+
const w = $(e, u);
|
|
493
490
|
return t.getSubForm(
|
|
494
491
|
w,
|
|
495
|
-
|
|
492
|
+
p
|
|
496
493
|
);
|
|
497
494
|
}
|
|
498
495
|
};
|
|
499
496
|
}
|
|
500
|
-
function
|
|
501
|
-
const e =
|
|
502
|
-
() => Object.freeze(g(t.initialData))
|
|
503
|
-
), r = L(g(e)), a = $({
|
|
497
|
+
function je(t) {
|
|
498
|
+
const e = v(() => g(t.initialData)), r = J(g(e)), a = _({
|
|
504
499
|
initialData: e,
|
|
505
500
|
data: r
|
|
506
501
|
});
|
|
@@ -511,20 +506,20 @@ function Oe(t) {
|
|
|
511
506
|
},
|
|
512
507
|
{ flush: "sync" }
|
|
513
508
|
);
|
|
514
|
-
const s =
|
|
509
|
+
const s = Pe(a, t), n = me(a, s, {
|
|
515
510
|
keepValuesOnUnmount: t.keepValuesOnUnmount,
|
|
516
511
|
onBlur: async (c) => {
|
|
517
512
|
d(t.validationStrategy) === "onTouch" && s.validateField(c);
|
|
518
513
|
}
|
|
519
|
-
}), f =
|
|
520
|
-
|
|
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);
|
|
521
516
|
}, y = () => {
|
|
522
517
|
r.value = g(e), s.reset();
|
|
523
518
|
for (const c of n.fields.value)
|
|
524
519
|
c.reset();
|
|
525
520
|
};
|
|
526
|
-
function V(c,
|
|
527
|
-
return
|
|
521
|
+
function V(c, o) {
|
|
522
|
+
return Se(l, c);
|
|
528
523
|
}
|
|
529
524
|
const l = {
|
|
530
525
|
...n,
|
|
@@ -538,7 +533,7 @@ function Oe(t) {
|
|
|
538
533
|
};
|
|
539
534
|
return d(t.validationStrategy) === "onFormOpen" && s.validateForm(), l;
|
|
540
535
|
}
|
|
541
|
-
const
|
|
536
|
+
const Oe = /* @__PURE__ */ k({
|
|
542
537
|
__name: "Field",
|
|
543
538
|
props: {
|
|
544
539
|
form: {},
|
|
@@ -553,10 +548,10 @@ const Ce = /* @__PURE__ */ k({
|
|
|
553
548
|
setup(t) {
|
|
554
549
|
const e = t, r = e.form.defineField({
|
|
555
550
|
path: e.path
|
|
556
|
-
}), a =
|
|
557
|
-
return (s, n) => b(s.$slots, "default", M(
|
|
551
|
+
}), a = _(r);
|
|
552
|
+
return (s, n) => b(s.$slots, "default", x(M(a)));
|
|
558
553
|
}
|
|
559
|
-
}),
|
|
554
|
+
}), Ce = /* @__PURE__ */ k({
|
|
560
555
|
inheritAttrs: !1,
|
|
561
556
|
__name: "FormFieldWrapper",
|
|
562
557
|
props: {
|
|
@@ -567,27 +562,27 @@ const Ce = /* @__PURE__ */ k({
|
|
|
567
562
|
},
|
|
568
563
|
setup(t) {
|
|
569
564
|
return (e, r) => {
|
|
570
|
-
const a =
|
|
571
|
-
return
|
|
572
|
-
form:
|
|
573
|
-
path:
|
|
565
|
+
const a = ae("Field");
|
|
566
|
+
return K(), z(a, {
|
|
567
|
+
form: t.form,
|
|
568
|
+
path: t.path
|
|
574
569
|
}, {
|
|
575
|
-
default:
|
|
576
|
-
(
|
|
570
|
+
default: A(({ errors: s, data: n, setData: f }) => [
|
|
571
|
+
(K(), z(se(t.component), ne({ ...t.componentProps, ...e.$attrs }, {
|
|
577
572
|
"model-value": n,
|
|
578
573
|
errors: s,
|
|
579
|
-
name:
|
|
574
|
+
name: t.path,
|
|
580
575
|
"onUpdate:modelValue": f
|
|
581
576
|
}), ie({
|
|
582
|
-
default:
|
|
577
|
+
default: A(() => [
|
|
583
578
|
b(e.$slots, "default")
|
|
584
579
|
]),
|
|
585
580
|
_: 2
|
|
586
581
|
}, [
|
|
587
|
-
|
|
582
|
+
oe(e.$slots, (m, y) => ({
|
|
588
583
|
name: y,
|
|
589
|
-
fn:
|
|
590
|
-
b(e.$slots, y, M(
|
|
584
|
+
fn: A((V) => [
|
|
585
|
+
b(e.$slots, y, x(M(V ?? {})))
|
|
591
586
|
])
|
|
592
587
|
}))
|
|
593
588
|
]), 1040, ["model-value", "errors", "name", "onUpdate:modelValue"]))
|
|
@@ -596,20 +591,20 @@ const Ce = /* @__PURE__ */ k({
|
|
|
596
591
|
}, 8, ["form", "path"]);
|
|
597
592
|
};
|
|
598
593
|
}
|
|
599
|
-
}),
|
|
594
|
+
}), Ie = /* @__PURE__ */ k({
|
|
600
595
|
__name: "FormPart",
|
|
601
596
|
props: {
|
|
602
597
|
form: {},
|
|
603
598
|
path: {}
|
|
604
599
|
},
|
|
605
600
|
setup(t) {
|
|
606
|
-
const e = t, r =
|
|
607
|
-
return (a, s) => b(a.$slots, "default", M(
|
|
601
|
+
const e = t, r = v(() => e.form.getSubForm(e.path));
|
|
602
|
+
return (a, s) => b(a.$slots, "default", x(M({ subform: r.value })));
|
|
608
603
|
}
|
|
609
604
|
});
|
|
610
605
|
export {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
606
|
+
Oe as Field,
|
|
607
|
+
Ce as FormFieldWrapper,
|
|
608
|
+
Ie as FormPart,
|
|
609
|
+
je as useForm
|
|
615
610
|
};
|
package/dist/types/form.d.ts
CHANGED
|
@@ -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>>) => Form<PickEntity<T, P
|
|
47
|
+
getSubForm: <P extends EntityPaths<T>>(path: P, options?: SubformOptions<PickEntity<T, P>>) => Omit<Form<PickEntity<T, P>>, 'submitHandler'>;
|
|
48
48
|
}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare function existsFieldPath<T, K extends Paths<T>>(field: FormField<
|
|
|
8
8
|
export declare function getNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPath<K>): PickProps<T, K>;
|
|
9
9
|
export declare function setNestedValue<T, K extends Paths<T>>(obj: MaybeRef<T>, path: K | SplitPath<K>, value: PickProps<T, K>): void;
|
|
10
10
|
export declare const getLens: <T, K extends Paths<T>>(data: MaybeRef<T>, key: MaybeRef<K | SplitPath<K>>) => WritableComputedRef<PickProps<T, K>, PickProps<T, K>>;
|
|
11
|
-
type JoinPath<Base extends string, Sub extends string> = `${Base}${Base extends
|
|
11
|
+
type JoinPath<Base extends string, Sub extends string> = `${Base}${Base extends '' ? '' : Sub extends '' ? '' : '.'}${Sub}`;
|
|
12
12
|
export declare function joinPath<Base extends string, Sub extends string>(basePath: Base, subPath: Sub): JoinPath<Base, Sub>;
|
|
13
13
|
export declare function filterErrorsForPath(errors: ErrorBag, path: string): ErrorBag;
|
|
14
14
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamnovu/kit-vue-forms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@vitest/ui": "^2.0.0",
|
|
27
27
|
"happy-dom": "^12.0.0",
|
|
28
28
|
"vitest": "^2.0.0",
|
|
29
|
-
"vue": "^3.5.
|
|
29
|
+
"vue": "^3.5.25"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@vueuse/core": "^13.5.0",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Awaitable } from "@vueuse/core";
|
|
1
2
|
import {
|
|
2
3
|
computed,
|
|
3
4
|
onScopeDispose,
|
|
@@ -6,65 +7,69 @@ import {
|
|
|
6
7
|
toRef,
|
|
7
8
|
triggerRef,
|
|
8
9
|
unref,
|
|
9
|
-
|
|
10
|
+
watch,
|
|
10
11
|
type MaybeRef,
|
|
11
|
-
} from
|
|
12
|
-
import type { FieldsTuple, FormDataDefault, FormField } from
|
|
13
|
-
import type { Paths, PickProps } from
|
|
14
|
-
import { existsPath, getLens, getNestedValue } from
|
|
15
|
-
import { Rc } from
|
|
16
|
-
import { useField, type UseFieldOptions } from
|
|
17
|
-
import type { ValidationState } from
|
|
18
|
-
import type { Awaitable } from '@vueuse/core'
|
|
12
|
+
} from "vue";
|
|
13
|
+
import type { FieldsTuple, FormDataDefault, FormField } from "../types/form";
|
|
14
|
+
import type { Paths, PickProps } from "../types/util";
|
|
15
|
+
import { existsPath, getLens, getNestedValue } from "../utils/path";
|
|
16
|
+
import { Rc } from "../utils/rc";
|
|
17
|
+
import { useField, type UseFieldOptions } from "./useField";
|
|
18
|
+
import type { ValidationState } from "./useValidation";
|
|
19
19
|
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
-
type FieldRegistryCache<T> = Map<Paths<T>, FormField<any, string
|
|
21
|
+
type FieldRegistryCache<T> = Map<Paths<T>, FormField<any, string>>;
|
|
22
22
|
|
|
23
23
|
export type ResolvedFormField<T, K extends Paths<T>> = FormField<
|
|
24
24
|
PickProps<T, K>,
|
|
25
25
|
K
|
|
26
|
-
|
|
26
|
+
>;
|
|
27
27
|
|
|
28
28
|
export type DefineFieldOptions<F, K extends string> = Pick<
|
|
29
29
|
UseFieldOptions<F, K>,
|
|
30
|
-
|
|
30
|
+
"path"
|
|
31
31
|
> & {
|
|
32
|
-
onBlur?: () => void
|
|
33
|
-
onFocus?: () => void
|
|
34
|
-
}
|
|
32
|
+
onBlur?: () => void;
|
|
33
|
+
onFocus?: () => void;
|
|
34
|
+
};
|
|
35
35
|
|
|
36
36
|
interface FormState<
|
|
37
37
|
T extends FormDataDefault,
|
|
38
38
|
TIn extends FormDataDefault = T,
|
|
39
39
|
> {
|
|
40
|
-
data: T
|
|
41
|
-
initialData: TIn
|
|
40
|
+
data: T;
|
|
41
|
+
initialData: TIn;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface FieldRegistryOptions {
|
|
45
|
-
keepValuesOnUnmount?: MaybeRef<boolean
|
|
46
|
-
onBlur?: (path: string) => Awaitable<void
|
|
47
|
-
onFocus?: (path: string) => Awaitable<void
|
|
45
|
+
keepValuesOnUnmount?: MaybeRef<boolean>;
|
|
46
|
+
onBlur?: (path: string) => Awaitable<void>;
|
|
47
|
+
onFocus?: (path: string) => Awaitable<void>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const optionDefaults = {
|
|
51
51
|
keepValuesOnUnmount: true,
|
|
52
|
-
}
|
|
52
|
+
};
|
|
53
53
|
|
|
54
54
|
// A computed that always reflects the latest value from the getter
|
|
55
55
|
// This computed forces updates even if the value is the same (to trigger watchers)
|
|
56
|
-
function
|
|
57
|
-
|
|
56
|
+
function initialDataSync<T extends FormDataDefault>(
|
|
57
|
+
formState: FormState<T>,
|
|
58
|
+
path: Paths<T>,
|
|
59
|
+
) {
|
|
60
|
+
const getNewValue = () => getNestedValue(formState.initialData, path);
|
|
61
|
+
const initialValueRef = shallowRef(getNewValue());
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
watch(
|
|
64
|
+
() => formState.initialData,
|
|
60
65
|
() => {
|
|
61
|
-
initialValueRef.value =
|
|
62
|
-
triggerRef(initialValueRef)
|
|
66
|
+
initialValueRef.value = getNewValue();
|
|
67
|
+
triggerRef(initialValueRef);
|
|
63
68
|
},
|
|
64
|
-
{ flush:
|
|
65
|
-
)
|
|
69
|
+
{ flush: "sync" },
|
|
70
|
+
);
|
|
66
71
|
|
|
67
|
-
return initialValueRef
|
|
72
|
+
return initialValueRef;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
export function useFieldRegistry<T extends FormDataDefault>(
|
|
@@ -72,104 +77,99 @@ export function useFieldRegistry<T extends FormDataDefault>(
|
|
|
72
77
|
validationState: ValidationState<T>,
|
|
73
78
|
fieldRegistryOptions?: FieldRegistryOptions,
|
|
74
79
|
) {
|
|
75
|
-
const fieldReferenceCounter = new Map<Paths<T>, Rc>()
|
|
76
|
-
const fields = shallowReactive(new Map()) as FieldRegistryCache<T
|
|
80
|
+
const fieldReferenceCounter = new Map<Paths<T>, Rc>();
|
|
81
|
+
const fields = shallowReactive(new Map()) as FieldRegistryCache<T>;
|
|
77
82
|
const registryOptions = {
|
|
78
83
|
...optionDefaults,
|
|
79
84
|
...fieldRegistryOptions,
|
|
80
|
-
}
|
|
85
|
+
};
|
|
81
86
|
|
|
82
87
|
const registerField = <K extends Paths<T>>(
|
|
83
88
|
field: ResolvedFormField<T, K>,
|
|
84
89
|
) => {
|
|
85
|
-
const path = unref(field.path) as Paths<T
|
|
86
|
-
fields.set(path, field)
|
|
87
|
-
}
|
|
90
|
+
const path = unref(field.path) as Paths<T>;
|
|
91
|
+
fields.set(path, field);
|
|
92
|
+
};
|
|
88
93
|
|
|
89
94
|
const deregisterField = (path: Paths<T>) => {
|
|
90
95
|
if (!registryOptions?.keepValuesOnUnmount) {
|
|
91
|
-
fields.get(path)?.reset()
|
|
96
|
+
fields.get(path)?.reset();
|
|
92
97
|
}
|
|
93
|
-
fields.delete(path)
|
|
94
|
-
}
|
|
98
|
+
fields.delete(path);
|
|
99
|
+
};
|
|
95
100
|
|
|
96
101
|
const track = (path: Paths<T>) => {
|
|
97
102
|
if (!fieldReferenceCounter.has(path)) {
|
|
98
|
-
fieldReferenceCounter.set(path, new Rc(() => deregisterField(path)))
|
|
103
|
+
fieldReferenceCounter.set(path, new Rc(() => deregisterField(path)));
|
|
99
104
|
} else {
|
|
100
|
-
fieldReferenceCounter.get(path)?.inc()
|
|
105
|
+
fieldReferenceCounter.get(path)?.inc();
|
|
101
106
|
}
|
|
102
|
-
}
|
|
107
|
+
};
|
|
103
108
|
|
|
104
109
|
const untrack = (path: Paths<T>) => {
|
|
105
110
|
if (fieldReferenceCounter.has(path)) {
|
|
106
|
-
fieldReferenceCounter.get(path)?.dec()
|
|
111
|
+
fieldReferenceCounter.get(path)?.dec();
|
|
107
112
|
}
|
|
108
|
-
}
|
|
113
|
+
};
|
|
109
114
|
|
|
110
115
|
const getField = <K extends Paths<T>>(
|
|
111
116
|
options: DefineFieldOptions<PickProps<T, K>, K>,
|
|
112
117
|
): ResolvedFormField<T, K> => {
|
|
113
|
-
const { path } = options
|
|
118
|
+
const { path } = options;
|
|
114
119
|
|
|
115
120
|
if (!fields.has(path)) {
|
|
116
121
|
const field = useField({
|
|
117
122
|
path,
|
|
118
|
-
value: getLens(toRef(formState,
|
|
119
|
-
initialValue:
|
|
120
|
-
getNestedValue(formState.initialData, path)),
|
|
123
|
+
value: getLens(toRef(formState, "data"), path),
|
|
124
|
+
initialValue: initialDataSync(formState, path),
|
|
121
125
|
existsInForm: computed(() => existsPath(formState.data, unref(path))),
|
|
122
126
|
errors: computed({
|
|
123
127
|
get() {
|
|
124
|
-
return validationState.errors.value.propertyErrors[path] || []
|
|
128
|
+
return validationState.errors.value.propertyErrors[path] || [];
|
|
125
129
|
},
|
|
126
130
|
set(newErrors) {
|
|
127
|
-
validationState.errors.value.propertyErrors[path] = newErrors
|
|
131
|
+
validationState.errors.value.propertyErrors[path] = newErrors;
|
|
128
132
|
},
|
|
129
133
|
}),
|
|
130
134
|
onBlur: async () => {
|
|
131
|
-
await Promise.all(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
],
|
|
136
|
-
)
|
|
135
|
+
await Promise.all([
|
|
136
|
+
registryOptions?.onBlur?.(unref(path)),
|
|
137
|
+
options.onBlur?.(),
|
|
138
|
+
]);
|
|
137
139
|
},
|
|
138
140
|
onFocus: async () => {
|
|
139
|
-
await Promise.all(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
],
|
|
144
|
-
)
|
|
141
|
+
await Promise.all([
|
|
142
|
+
registryOptions?.onFocus?.(unref(path)),
|
|
143
|
+
options.onFocus?.(),
|
|
144
|
+
]);
|
|
145
145
|
},
|
|
146
|
-
})
|
|
146
|
+
});
|
|
147
147
|
|
|
148
|
-
registerField(field)
|
|
148
|
+
registerField(field);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
const field = fields.get(path) as ResolvedFormField<T, K
|
|
151
|
+
const field = fields.get(path) as ResolvedFormField<T, K>;
|
|
152
152
|
|
|
153
|
-
track(path)
|
|
153
|
+
track(path);
|
|
154
154
|
|
|
155
155
|
// Clean up field on unmount
|
|
156
156
|
onScopeDispose(() => {
|
|
157
|
-
untrack(path)
|
|
158
|
-
})
|
|
157
|
+
untrack(path);
|
|
158
|
+
});
|
|
159
159
|
|
|
160
|
-
return field
|
|
161
|
-
}
|
|
160
|
+
return field;
|
|
161
|
+
};
|
|
162
162
|
|
|
163
163
|
const defineField = <K extends Paths<T>>(
|
|
164
164
|
options: DefineFieldOptions<PickProps<T, K>, K>,
|
|
165
165
|
): ResolvedFormField<T, K> => {
|
|
166
|
-
const field = getField(options)
|
|
166
|
+
const field = getField(options);
|
|
167
167
|
|
|
168
168
|
// TODO: If more options are ever needed than only the path we have to update the field
|
|
169
169
|
// here with the new options
|
|
170
170
|
|
|
171
|
-
return field
|
|
172
|
-
}
|
|
171
|
+
return field;
|
|
172
|
+
};
|
|
173
173
|
|
|
174
174
|
return {
|
|
175
175
|
fields: computed(() => [...fields.values()] as FieldsTuple<T>),
|
|
@@ -177,9 +177,9 @@ export function useFieldRegistry<T extends FormDataDefault>(
|
|
|
177
177
|
registerField,
|
|
178
178
|
deregisterField,
|
|
179
179
|
defineField,
|
|
180
|
-
}
|
|
180
|
+
};
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
export type FieldRegistry<T extends FormDataDefault> = ReturnType<
|
|
184
184
|
typeof useFieldRegistry<T>
|
|
185
|
-
|
|
185
|
+
>;
|
|
@@ -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,86 +9,81 @@ 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
|
|
21
|
-
|
|
22
|
-
// TODO @Elias implement validation strategy handling
|
|
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'
|
|
23
21
|
|
|
24
22
|
export interface UseFormOptions<T extends FormDataDefault>
|
|
25
23
|
extends ValidationOptions<T> {
|
|
26
|
-
initialData: MaybeRefOrGetter<T
|
|
27
|
-
validationStrategy?: MaybeRef<ValidationStrategy
|
|
28
|
-
keepValuesOnUnmount?: MaybeRef<boolean
|
|
24
|
+
initialData: MaybeRefOrGetter<T>
|
|
25
|
+
validationStrategy?: MaybeRef<ValidationStrategy>
|
|
26
|
+
keepValuesOnUnmount?: MaybeRef<boolean>
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
|
|
32
|
-
const initialData = computed(() =>
|
|
33
|
-
Object.freeze(cloneRefValue(options.initialData)),
|
|
34
|
-
);
|
|
30
|
+
const initialData = computed(() => cloneRefValue(options.initialData))
|
|
35
31
|
|
|
36
|
-
const data = ref<T>(cloneRefValue(initialData)) as Ref<T
|
|
32
|
+
const data = ref<T>(cloneRefValue(initialData)) as Ref<T>
|
|
37
33
|
|
|
38
34
|
const state = reactive({
|
|
39
35
|
initialData,
|
|
40
36
|
data,
|
|
41
|
-
})
|
|
37
|
+
})
|
|
42
38
|
|
|
43
39
|
watch(
|
|
44
40
|
initialData,
|
|
45
41
|
(newValue) => {
|
|
46
|
-
state.data = cloneRefValue(newValue)
|
|
42
|
+
state.data = cloneRefValue(newValue)
|
|
47
43
|
},
|
|
48
|
-
{ flush:
|
|
49
|
-
)
|
|
44
|
+
{ flush: 'sync' },
|
|
45
|
+
)
|
|
50
46
|
|
|
51
|
-
const validationState = useValidation(state, options)
|
|
47
|
+
const validationState = useValidation(state, options)
|
|
52
48
|
const fieldRegistry = useFieldRegistry(state, validationState, {
|
|
53
49
|
keepValuesOnUnmount: options.keepValuesOnUnmount,
|
|
54
50
|
onBlur: async (path: string) => {
|
|
55
|
-
if (unref(options.validationStrategy) ===
|
|
56
|
-
|
|
57
|
-
validationState.validateField(path);
|
|
51
|
+
if (unref(options.validationStrategy) === 'onTouch') {
|
|
52
|
+
validationState.validateField(path)
|
|
58
53
|
}
|
|
59
54
|
},
|
|
60
|
-
})
|
|
61
|
-
const formState = useFormState(fieldRegistry)
|
|
55
|
+
})
|
|
56
|
+
const formState = useFormState(fieldRegistry)
|
|
62
57
|
|
|
63
58
|
const submitHandler = (onSubmit: (data: T) => Awaitable<void>) => {
|
|
64
59
|
return async (event: SubmitEvent) => {
|
|
65
|
-
event.preventDefault()
|
|
60
|
+
event.preventDefault()
|
|
66
61
|
|
|
67
|
-
if (unref(options.validationStrategy) !==
|
|
68
|
-
await validationState.validateForm()
|
|
62
|
+
if (unref(options.validationStrategy) !== 'none') {
|
|
63
|
+
await validationState.validateForm()
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
if (!validationState.isValid.value) {
|
|
72
|
-
return
|
|
67
|
+
return
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
await onSubmit(state.data)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
70
|
+
await onSubmit(state.data)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
78
73
|
|
|
79
74
|
const reset = () => {
|
|
80
|
-
data.value = cloneRefValue(initialData)
|
|
81
|
-
validationState.reset()
|
|
75
|
+
data.value = cloneRefValue(initialData)
|
|
76
|
+
validationState.reset()
|
|
82
77
|
for (const field of fieldRegistry.fields.value) {
|
|
83
|
-
field.reset()
|
|
78
|
+
field.reset()
|
|
84
79
|
}
|
|
85
|
-
}
|
|
80
|
+
}
|
|
86
81
|
|
|
87
82
|
function getSubForm<K extends EntityPaths<T>>(
|
|
88
83
|
path: K,
|
|
89
84
|
options?: SubformOptions<PickEntity<T, K>>,
|
|
90
|
-
): Form<PickEntity<T, K
|
|
91
|
-
return createSubformInterface(formInterface, path, options)
|
|
85
|
+
): Omit<Form<PickEntity<T, K>>, 'submitHandler'> {
|
|
86
|
+
return createSubformInterface(formInterface, path, options)
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
const formInterface: Form<T> = {
|
|
@@ -98,13 +93,13 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
|
|
|
98
93
|
reset,
|
|
99
94
|
getSubForm,
|
|
100
95
|
submitHandler,
|
|
101
|
-
initialData: toRef(state,
|
|
102
|
-
data: toRef(state,
|
|
103
|
-
}
|
|
96
|
+
initialData: toRef(state, 'initialData') as Form<T>['initialData'],
|
|
97
|
+
data: toRef(state, 'data') as Form<T>['data'],
|
|
98
|
+
}
|
|
104
99
|
|
|
105
|
-
if (unref(options.validationStrategy) ===
|
|
106
|
-
validationState.validateForm()
|
|
100
|
+
if (unref(options.validationStrategy) === 'onFormOpen') {
|
|
101
|
+
validationState.validateForm()
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
return formInterface
|
|
104
|
+
return formInterface
|
|
110
105
|
}
|
|
@@ -50,7 +50,7 @@ export function createSubformInterface<
|
|
|
50
50
|
mainForm: Form<T>,
|
|
51
51
|
path: K,
|
|
52
52
|
_options?: SubformOptions<PickEntity<T, K>>,
|
|
53
|
-
): Form<PickEntity<T, K
|
|
53
|
+
): Omit<Form<PickEntity<T, K>>, 'submitHandler'> {
|
|
54
54
|
type ST = PickEntity<T, K>
|
|
55
55
|
type SP = Paths<ST>
|
|
56
56
|
type MP<P extends SP> = `${K}.${P}`
|
package/src/types/form.ts
CHANGED
package/src/utils/path.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { computed, isRef, unref, type MaybeRef } from
|
|
2
|
-
import type { Paths, PickProps, SplitPath } from
|
|
3
|
-
import type { ErrorBag, ValidationErrors } from
|
|
4
|
-
import type { FormField } from
|
|
1
|
+
import { computed, isRef, unref, type MaybeRef } from 'vue'
|
|
2
|
+
import type { Paths, PickProps, SplitPath } from '../types/util'
|
|
3
|
+
import type { ErrorBag, ValidationErrors } from '../types/validation'
|
|
4
|
+
import type { FormField } from '../types/form'
|
|
5
5
|
|
|
6
6
|
export function splitPath(path: string): string[] {
|
|
7
|
-
if (path ===
|
|
8
|
-
return []
|
|
7
|
+
if (path === '') {
|
|
8
|
+
return []
|
|
9
9
|
}
|
|
10
|
-
return path.split(/\s*\.\s*/).filter(Boolean)
|
|
10
|
+
return path.split(/\s*\.\s*/).filter(Boolean)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function existsPath<T, K extends Paths<T>>(
|
|
14
14
|
obj: T,
|
|
15
15
|
path: K | SplitPath<K>,
|
|
16
16
|
): boolean {
|
|
17
|
-
const splittedPath = Array.isArray(path) ? path : splitPath(path) as SplitPath<K
|
|
18
|
-
return !!getNestedValue(obj, splittedPath.slice(0, -1) as SplitPath<K>)
|
|
17
|
+
const splittedPath = Array.isArray(path) ? path : splitPath(path) as SplitPath<K>
|
|
18
|
+
return !!getNestedValue(obj, splittedPath.slice(0, -1) as SplitPath<K>)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function existsFieldPath<T, K extends Paths<T>>(field: FormField<T, K>) {
|
|
@@ -26,11 +26,11 @@ export function getNestedValue<T, K extends Paths<T>>(
|
|
|
26
26
|
obj: T,
|
|
27
27
|
path: K | SplitPath<K>,
|
|
28
28
|
) {
|
|
29
|
-
const splittedPath = Array.isArray(path) ? path : splitPath(path)
|
|
29
|
+
const splittedPath = Array.isArray(path) ? path : splitPath(path)
|
|
30
30
|
return splittedPath.reduce(
|
|
31
31
|
(current, key) => current?.[key],
|
|
32
32
|
obj as Record<string, never>,
|
|
33
|
-
) as PickProps<T, K
|
|
33
|
+
) as PickProps<T, K>
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function setNestedValue<T, K extends Paths<T>>(
|
|
@@ -38,31 +38,31 @@ export function setNestedValue<T, K extends Paths<T>>(
|
|
|
38
38
|
path: K | SplitPath<K>,
|
|
39
39
|
value: PickProps<T, K>,
|
|
40
40
|
): void {
|
|
41
|
-
const keys = Array.isArray(path) ? path : splitPath(path)
|
|
41
|
+
const keys = Array.isArray(path) ? path : splitPath(path)
|
|
42
42
|
|
|
43
|
-
const lastKey = keys.at(-1)
|
|
43
|
+
const lastKey = keys.at(-1)!
|
|
44
44
|
|
|
45
45
|
if (!lastKey) {
|
|
46
46
|
if (!isRef(obj)) {
|
|
47
47
|
// We cannot do anything here as we have nothing we can assign to
|
|
48
|
-
return
|
|
48
|
+
return
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
obj.value = value
|
|
51
|
+
obj.value = value
|
|
52
52
|
} else {
|
|
53
53
|
const target = keys.slice(0, -1).reduce(
|
|
54
54
|
(current, key) => {
|
|
55
55
|
if (current?.[key] === undefined) {
|
|
56
56
|
// Create the nested object if it doesn't exist
|
|
57
|
-
current[key] = {}
|
|
57
|
+
current[key] = {}
|
|
58
58
|
}
|
|
59
|
-
return current?.[key]
|
|
59
|
+
return current?.[key]
|
|
60
60
|
},
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
62
|
unref(obj) as Record<string, any>,
|
|
63
|
-
)
|
|
63
|
+
)
|
|
64
64
|
|
|
65
|
-
target[lastKey] = value
|
|
65
|
+
target[lastKey] = value
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -72,58 +72,57 @@ export const getLens = <T, K extends Paths<T>>(
|
|
|
72
72
|
) => {
|
|
73
73
|
return computed({
|
|
74
74
|
get() {
|
|
75
|
-
return getNestedValue(unref(data), unref(key))
|
|
75
|
+
return getNestedValue(unref(data), unref(key))
|
|
76
76
|
},
|
|
77
77
|
set(value: PickProps<T, K>) {
|
|
78
|
-
setNestedValue(data, unref(key), value)
|
|
78
|
+
setNestedValue(data, unref(key), value)
|
|
79
79
|
},
|
|
80
|
-
})
|
|
81
|
-
}
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
82
|
|
|
83
83
|
type JoinPath<
|
|
84
84
|
Base extends string,
|
|
85
85
|
Sub extends string,
|
|
86
|
-
> = `${Base}${Base extends
|
|
86
|
+
> = `${Base}${Base extends '' ? '' : Sub extends '' ? '' : '.'}${Sub}`
|
|
87
87
|
export function joinPath<Base extends string, Sub extends string>(
|
|
88
88
|
basePath: Base,
|
|
89
89
|
subPath: Sub,
|
|
90
90
|
): JoinPath<Base, Sub> {
|
|
91
91
|
if (!basePath && !subPath) {
|
|
92
|
-
return
|
|
92
|
+
return '' as JoinPath<Base, Sub>
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
if (!basePath && subPath) {
|
|
96
|
-
return subPath as JoinPath<Base, Sub
|
|
96
|
+
return subPath as JoinPath<Base, Sub>
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
if (!subPath && basePath) {
|
|
100
|
-
return basePath as JoinPath<Base, Sub
|
|
100
|
+
return basePath as JoinPath<Base, Sub>
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
return `${basePath}.${subPath}` as JoinPath<Base, Sub
|
|
103
|
+
return `${basePath}.${subPath}` as JoinPath<Base, Sub>
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export function filterErrorsForPath(errors: ErrorBag, path: string): ErrorBag {
|
|
107
107
|
// Handle empty path - return all errors
|
|
108
108
|
if (!path) {
|
|
109
|
-
return errors
|
|
109
|
+
return errors
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
const pathPrefix = `${path}
|
|
113
|
-
const filteredPropertyErrors: Record<string, ValidationErrors> =
|
|
114
|
-
Object.
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
112
|
+
const pathPrefix = `${path}.`
|
|
113
|
+
const filteredPropertyErrors: Record<string, ValidationErrors> = Object.fromEntries(
|
|
114
|
+
Object.entries(errors.propertyErrors)
|
|
115
|
+
.filter(([errorPath]) => {
|
|
116
|
+
return errorPath.startsWith(pathPrefix)
|
|
117
|
+
})
|
|
118
|
+
.map(([errorPath, errorMessages]) => [
|
|
119
|
+
errorPath.slice(pathPrefix.length),
|
|
120
|
+
errorMessages,
|
|
121
|
+
]),
|
|
122
|
+
)
|
|
124
123
|
|
|
125
124
|
return {
|
|
126
125
|
general: errors.general, // Keep general errors
|
|
127
126
|
propertyErrors: filteredPropertyErrors,
|
|
128
|
-
}
|
|
127
|
+
}
|
|
129
128
|
}
|