@teamnovu/kit-vue-forms 0.0.16 → 0.0.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/components/FormFieldWrapper.vue.d.ts +25 -0
- package/dist/components/FormPart.vue.d.ts +24 -0
- package/dist/index.d.ts +5 -0
- package/dist/{index.mjs → index.js} +185 -141
- package/dist/types/FormComponentProps.d.ts +8 -0
- package/dist/types/util.d.ts +1 -1
- package/package.json +3 -1
- package/src/components/FormFieldWrapper.vue +42 -0
- package/src/components/FormPart.vue +22 -0
- package/src/composables/useForm.ts +0 -1
- package/src/index.ts +9 -0
- package/src/types/FormComponentProps.ts +14 -0
- package/src/types/util.ts +1 -1
- package/tests/formState.test.ts +10 -3
- package/tests/subform.test.ts +19 -0
- package/tests/useForm.test.ts +22 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Component, VNodeProps, AllowedComponentProps, ComponentCustomProps, PublicProps, ShallowUnwrapRef, VNode } from 'vue';
|
|
2
|
+
import { ComponentProps } from 'vue-component-type-helpers';
|
|
3
|
+
import { Paths } from '../types/util.ts';
|
|
4
|
+
import { Form } from '../types/form.ts';
|
|
5
|
+
export interface FormFieldWrapperProps<TData extends object, TPath extends string, TComponent> {
|
|
6
|
+
component: TComponent;
|
|
7
|
+
componentProps: Omit<ComponentProps<TComponent>, 'modelValue' | 'update:modelValue' | 'errors'>;
|
|
8
|
+
form: Form<TData>;
|
|
9
|
+
path: TPath;
|
|
10
|
+
}
|
|
11
|
+
declare const _default: <TData extends object, TPath extends Paths<TData>, TComponent extends Component>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
12
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{} & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>, never> & FormFieldWrapperProps<TData, TPath, TComponent> & Partial<{}>> & PublicProps;
|
|
13
|
+
expose(exposed: ShallowUnwrapRef<{}>): void;
|
|
14
|
+
attrs: any;
|
|
15
|
+
slots: {
|
|
16
|
+
default?(_: {}): any;
|
|
17
|
+
};
|
|
18
|
+
emit: {};
|
|
19
|
+
}>) => VNode & {
|
|
20
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
21
|
+
};
|
|
22
|
+
export default _default;
|
|
23
|
+
type __VLS_PrettifyLocal<T> = {
|
|
24
|
+
[K in keyof T]: T[K];
|
|
25
|
+
} & {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Form } from '../types/form.ts';
|
|
2
|
+
import { EntityPaths, PickEntity } from '../types/util.ts';
|
|
3
|
+
import { VNodeProps, AllowedComponentProps, ComponentCustomProps, PublicProps, ShallowUnwrapRef, VNode } from 'vue';
|
|
4
|
+
export interface FormPartProps<TData extends object, TPath> {
|
|
5
|
+
form: Form<TData>;
|
|
6
|
+
path: TPath;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: <TData extends object, TPath extends EntityPaths<TData>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
9
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{} & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>, never> & FormPartProps<TData, TPath> & Partial<{}>> & PublicProps;
|
|
10
|
+
expose(exposed: ShallowUnwrapRef<{}>): void;
|
|
11
|
+
attrs: any;
|
|
12
|
+
slots: {
|
|
13
|
+
default?(_: {
|
|
14
|
+
subform: Form< PickEntity<TData, TPath>>;
|
|
15
|
+
}): any;
|
|
16
|
+
};
|
|
17
|
+
emit: {};
|
|
18
|
+
}>) => VNode & {
|
|
19
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
20
|
+
};
|
|
21
|
+
export default _default;
|
|
22
|
+
type __VLS_PrettifyLocal<T> = {
|
|
23
|
+
[K in keyof T]: T[K];
|
|
24
|
+
} & {};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,10 @@ export type { ValidationStrategy, ValidationErrorMessage as ErrorMessage, Valida
|
|
|
5
5
|
export type { DeepPartial } from './utils/type-helpers';
|
|
6
6
|
export type { Form, FormField } from './types/form';
|
|
7
7
|
export type { SplitPath, Paths, PickProps, ObjectOf, EntityPaths } from './types/util';
|
|
8
|
+
export type { FormComponentProps, ExcludedFieldProps } from './types/FormComponentProps';
|
|
8
9
|
export { default as Field } from './components/Field.vue';
|
|
9
10
|
export type { FieldProps } from './components/Field.vue';
|
|
11
|
+
export { default as FormFieldWrapper } from './components/FormFieldWrapper.vue';
|
|
12
|
+
export type { FormFieldWrapperProps } from './components/FormFieldWrapper.vue';
|
|
13
|
+
export { default as FormPart } from './components/FormPart.vue';
|
|
14
|
+
export type { FormPartProps } from './components/FormPart.vue';
|
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { toValue as
|
|
1
|
+
var T = Object.defineProperty;
|
|
2
|
+
var G = (e, r, t) => r in e ? T(e, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[r] = t;
|
|
3
|
+
var _ = (e, r, t) => G(e, typeof r != "symbol" ? r + "" : r, t);
|
|
4
|
+
import { toValue as L, toRaw as Z, computed as u, unref as d, reactive as E, toRefs as N, toRef as D, ref as W, watch as y, isRef as z, getCurrentScope as q, onBeforeUnmount as H, defineComponent as S, renderSlot as j, normalizeProps as M, guardReactiveProps as B, resolveComponent as Q, createBlock as O, openBlock as $, withCtx as A, resolveDynamicComponent as X, mergeProps as Y } from "vue";
|
|
5
5
|
import "zod";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const r = W(e), t = I(r);
|
|
6
|
+
function g(e) {
|
|
7
|
+
const r = L(e), t = Z(r);
|
|
9
8
|
return structuredClone(t);
|
|
10
9
|
}
|
|
11
|
-
function
|
|
10
|
+
function K(e) {
|
|
12
11
|
return e === "" ? [] : e.split(/\s*\.\s*/).filter(Boolean);
|
|
13
12
|
}
|
|
14
13
|
function w(e, r) {
|
|
15
|
-
return (Array.isArray(r) ? r :
|
|
14
|
+
return (Array.isArray(r) ? r : K(r)).reduce(
|
|
16
15
|
(s, a) => s == null ? void 0 : s[a],
|
|
17
16
|
e
|
|
18
17
|
);
|
|
19
18
|
}
|
|
20
|
-
function
|
|
21
|
-
const s = Array.isArray(r) ? r :
|
|
19
|
+
function x(e, r, t) {
|
|
20
|
+
const s = Array.isArray(r) ? r : K(r);
|
|
22
21
|
if (s.length === 0)
|
|
23
22
|
throw new Error("Path cannot be empty");
|
|
24
23
|
const a = s.at(-1), o = s.slice(0, -1).reduce(
|
|
25
|
-
(
|
|
24
|
+
(h, c) => h[c],
|
|
26
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
26
|
e
|
|
28
27
|
);
|
|
29
28
|
o[a] = t;
|
|
30
29
|
}
|
|
31
|
-
const
|
|
30
|
+
const U = (e, r) => u({
|
|
32
31
|
get() {
|
|
33
32
|
return w(d(e), d(r));
|
|
34
33
|
},
|
|
35
34
|
set(t) {
|
|
36
|
-
|
|
35
|
+
x(d(e), d(r), t);
|
|
37
36
|
}
|
|
38
37
|
});
|
|
39
38
|
function F(e, r) {
|
|
40
39
|
return !e && !r ? "" : !e && r ? r : !r && e ? e : `${e}.${r}`;
|
|
41
40
|
}
|
|
42
|
-
function
|
|
41
|
+
function rr(e, r) {
|
|
43
42
|
if (!r)
|
|
44
43
|
return e;
|
|
45
44
|
const t = `${r}.`, s = Object.fromEntries(
|
|
@@ -53,11 +52,11 @@ function k(e, r) {
|
|
|
53
52
|
propertyErrors: s
|
|
54
53
|
};
|
|
55
54
|
}
|
|
56
|
-
function
|
|
57
|
-
const r =
|
|
55
|
+
function er(e) {
|
|
56
|
+
const r = E({
|
|
58
57
|
value: e.value,
|
|
59
58
|
path: e.path,
|
|
60
|
-
initialValue: u(() => Object.freeze(
|
|
59
|
+
initialValue: u(() => Object.freeze(g(e.initialValue))),
|
|
61
60
|
errors: e.errors,
|
|
62
61
|
touched: !1
|
|
63
62
|
}), t = u(() => JSON.stringify(r.value) !== JSON.stringify(r.initialValue)), s = (f) => {
|
|
@@ -65,13 +64,13 @@ function q(e) {
|
|
|
65
64
|
}, a = () => {
|
|
66
65
|
r.touched = !0;
|
|
67
66
|
}, o = () => {
|
|
68
|
-
},
|
|
69
|
-
r.value =
|
|
67
|
+
}, h = () => {
|
|
68
|
+
r.value = g(r.initialValue), r.touched = !1, r.errors = [];
|
|
70
69
|
}, c = (f) => {
|
|
71
70
|
r.errors = f;
|
|
72
|
-
},
|
|
71
|
+
}, n = () => {
|
|
73
72
|
r.errors = [];
|
|
74
|
-
}, l =
|
|
73
|
+
}, l = N(r);
|
|
75
74
|
return {
|
|
76
75
|
data: l.value,
|
|
77
76
|
path: l.path,
|
|
@@ -82,20 +81,20 @@ function q(e) {
|
|
|
82
81
|
setData: s,
|
|
83
82
|
onBlur: a,
|
|
84
83
|
onFocus: o,
|
|
85
|
-
reset:
|
|
84
|
+
reset: h,
|
|
86
85
|
setErrors: c,
|
|
87
|
-
clearErrors:
|
|
86
|
+
clearErrors: n
|
|
88
87
|
};
|
|
89
88
|
}
|
|
90
|
-
function
|
|
89
|
+
function tr(e, r) {
|
|
91
90
|
const t = {}, s = (c) => {
|
|
92
|
-
const
|
|
93
|
-
t[
|
|
91
|
+
const n = d(c.path);
|
|
92
|
+
t[n] = c;
|
|
94
93
|
}, a = (c) => {
|
|
95
94
|
if (!t[c]) {
|
|
96
|
-
const
|
|
95
|
+
const n = er({
|
|
97
96
|
path: c,
|
|
98
|
-
value:
|
|
97
|
+
value: U(D(e, "data"), c),
|
|
99
98
|
initialValue: u(() => w(e.initialData, c)),
|
|
100
99
|
errors: u({
|
|
101
100
|
get() {
|
|
@@ -106,7 +105,7 @@ function H(e, r) {
|
|
|
106
105
|
}
|
|
107
106
|
})
|
|
108
107
|
});
|
|
109
|
-
return s(
|
|
108
|
+
return s(n), n;
|
|
110
109
|
}
|
|
111
110
|
return t[c];
|
|
112
111
|
};
|
|
@@ -118,19 +117,19 @@ function H(e, r) {
|
|
|
118
117
|
defineField: (c) => a(c.path)
|
|
119
118
|
};
|
|
120
119
|
}
|
|
121
|
-
function
|
|
120
|
+
function sr(e) {
|
|
122
121
|
const r = u(() => e.getFields().some((s) => d(s.dirty))), t = u(() => e.getFields().some((s) => d(s.touched)));
|
|
123
122
|
return {
|
|
124
123
|
isDirty: r,
|
|
125
124
|
isTouched: t
|
|
126
125
|
};
|
|
127
126
|
}
|
|
128
|
-
function
|
|
127
|
+
function ar(e) {
|
|
129
128
|
return e.filter(
|
|
130
129
|
(r, t, s) => s.indexOf(r) === t
|
|
131
130
|
);
|
|
132
131
|
}
|
|
133
|
-
function
|
|
132
|
+
function k(...e) {
|
|
134
133
|
return e.slice(1).reduce((r, t) => {
|
|
135
134
|
if (!r && !t)
|
|
136
135
|
return;
|
|
@@ -140,19 +139,19 @@ function C(...e) {
|
|
|
140
139
|
if (!s)
|
|
141
140
|
return r;
|
|
142
141
|
const a = (r ?? []).concat(t);
|
|
143
|
-
return
|
|
142
|
+
return ar(a);
|
|
144
143
|
}, e[0]);
|
|
145
144
|
}
|
|
146
|
-
function
|
|
145
|
+
function or(...e) {
|
|
147
146
|
return e.map((t) => Object.keys(t)).flat().reduce((t, s) => {
|
|
148
147
|
const a = e.map((o) => o[s]).filter(Boolean);
|
|
149
148
|
return {
|
|
150
149
|
...t,
|
|
151
|
-
[s]:
|
|
150
|
+
[s]: k(...a)
|
|
152
151
|
};
|
|
153
152
|
}, {});
|
|
154
153
|
}
|
|
155
|
-
function
|
|
154
|
+
function R(...e) {
|
|
156
155
|
if (!e.length)
|
|
157
156
|
return {
|
|
158
157
|
general: [],
|
|
@@ -161,18 +160,18 @@ function P(...e) {
|
|
|
161
160
|
const r = e[0];
|
|
162
161
|
return e.length === 1 ? r : e.slice(1).reduce(
|
|
163
162
|
(t, s) => ({
|
|
164
|
-
general:
|
|
165
|
-
propertyErrors:
|
|
163
|
+
general: k(t.general, s.general),
|
|
164
|
+
propertyErrors: or(t.propertyErrors ?? {}, s.propertyErrors ?? {})
|
|
166
165
|
}),
|
|
167
166
|
r
|
|
168
167
|
);
|
|
169
168
|
}
|
|
170
|
-
function
|
|
169
|
+
function C(e) {
|
|
171
170
|
var s;
|
|
172
171
|
const r = (((s = e.general) == null ? void 0 : s.length) ?? 0) > 0, t = Object.entries(e.propertyErrors).filter(([, a]) => a == null ? void 0 : a.length).length > 0;
|
|
173
172
|
return r || t;
|
|
174
173
|
}
|
|
175
|
-
function
|
|
174
|
+
function nr(e) {
|
|
176
175
|
const r = e.issues.filter((s) => s.path.length === 0).map((s) => s.message), t = e.issues.filter((s) => s.path.length > 0).reduce((s, a) => {
|
|
177
176
|
const o = a.path.join(".");
|
|
178
177
|
return {
|
|
@@ -185,24 +184,24 @@ function rr(e) {
|
|
|
185
184
|
propertyErrors: t
|
|
186
185
|
};
|
|
187
186
|
}
|
|
188
|
-
const
|
|
187
|
+
const m = {
|
|
189
188
|
isValid: !0,
|
|
190
189
|
errors: {
|
|
191
190
|
general: [],
|
|
192
191
|
propertyErrors: {}
|
|
193
192
|
}
|
|
194
193
|
};
|
|
195
|
-
class
|
|
194
|
+
class ir {
|
|
196
195
|
constructor(r) {
|
|
197
196
|
this.schema = r;
|
|
198
197
|
}
|
|
199
198
|
async validate(r) {
|
|
200
199
|
if (!this.schema)
|
|
201
|
-
return
|
|
200
|
+
return m;
|
|
202
201
|
const t = await this.schema.safeParseAsync(r);
|
|
203
202
|
if (t.success)
|
|
204
|
-
return
|
|
205
|
-
const s =
|
|
203
|
+
return m;
|
|
204
|
+
const s = nr(t.error);
|
|
206
205
|
return {
|
|
207
206
|
isValid: !1,
|
|
208
207
|
errors: {
|
|
@@ -212,16 +211,16 @@ class er {
|
|
|
212
211
|
};
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
|
-
class
|
|
214
|
+
class lr {
|
|
216
215
|
constructor(r) {
|
|
217
216
|
this.validateFn = r;
|
|
218
217
|
}
|
|
219
218
|
async validate(r) {
|
|
220
219
|
if (!this.validateFn)
|
|
221
|
-
return
|
|
220
|
+
return m;
|
|
222
221
|
try {
|
|
223
222
|
const t = await this.validateFn(r);
|
|
224
|
-
return t.isValid ?
|
|
223
|
+
return t.isValid ? m : t;
|
|
225
224
|
} catch (t) {
|
|
226
225
|
return {
|
|
227
226
|
isValid: !1,
|
|
@@ -233,11 +232,11 @@ class tr {
|
|
|
233
232
|
}
|
|
234
233
|
}
|
|
235
234
|
}
|
|
236
|
-
class
|
|
235
|
+
class cr {
|
|
237
236
|
constructor(r, t) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.schema = r, this.validateFn = t, this.schemaValidator = new
|
|
237
|
+
_(this, "schemaValidator");
|
|
238
|
+
_(this, "functionValidator");
|
|
239
|
+
this.schema = r, this.validateFn = t, this.schemaValidator = new ir(this.schema), this.functionValidator = new lr(this.validateFn);
|
|
241
240
|
}
|
|
242
241
|
async validate(r) {
|
|
243
242
|
const [t, s] = await Promise.all([
|
|
@@ -246,85 +245,85 @@ class sr {
|
|
|
246
245
|
]);
|
|
247
246
|
return {
|
|
248
247
|
isValid: t.isValid && s.isValid,
|
|
249
|
-
errors:
|
|
248
|
+
errors: R(t.errors, s.errors)
|
|
250
249
|
};
|
|
251
250
|
}
|
|
252
251
|
}
|
|
253
252
|
function b(e) {
|
|
254
|
-
return u(() => new
|
|
253
|
+
return u(() => new cr(
|
|
255
254
|
d(e.schema),
|
|
256
255
|
d(e.validateFn)
|
|
257
256
|
));
|
|
258
257
|
}
|
|
259
|
-
function
|
|
260
|
-
const t =
|
|
261
|
-
validators:
|
|
258
|
+
function ur(e, r) {
|
|
259
|
+
const t = E({
|
|
260
|
+
validators: W([b(r)]),
|
|
262
261
|
isValidated: !1,
|
|
263
|
-
errors: d(r.errors) ??
|
|
262
|
+
errors: d(r.errors) ?? m.errors
|
|
264
263
|
});
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
o(
|
|
268
|
-
}, { immediate: !0 }),
|
|
264
|
+
y(() => d(r.errors), async () => {
|
|
265
|
+
const n = await a();
|
|
266
|
+
o(n.errors);
|
|
267
|
+
}, { immediate: !0 }), y(
|
|
269
268
|
[() => t.validators],
|
|
270
|
-
async (
|
|
269
|
+
async (n) => {
|
|
271
270
|
if (t.isValidated)
|
|
272
|
-
if (
|
|
271
|
+
if (n) {
|
|
273
272
|
const l = await a();
|
|
274
273
|
t.errors = l.errors;
|
|
275
274
|
} else
|
|
276
|
-
t.errors =
|
|
275
|
+
t.errors = m.errors;
|
|
277
276
|
},
|
|
278
277
|
{ immediate: !0 }
|
|
279
|
-
),
|
|
280
|
-
t.isValidated &&
|
|
278
|
+
), y(() => e.data, () => {
|
|
279
|
+
t.isValidated && h();
|
|
281
280
|
});
|
|
282
|
-
const s = (
|
|
283
|
-
const l =
|
|
284
|
-
return t.validators.push(l),
|
|
281
|
+
const s = (n) => {
|
|
282
|
+
const l = z(n) ? n : b(n);
|
|
283
|
+
return t.validators.push(l), q() && H(() => {
|
|
285
284
|
t.validators = t.validators.filter(
|
|
286
285
|
(f) => f !== l
|
|
287
286
|
);
|
|
288
287
|
}), l;
|
|
289
288
|
};
|
|
290
289
|
async function a() {
|
|
291
|
-
const
|
|
292
|
-
t.validators.filter((
|
|
293
|
-
), l =
|
|
294
|
-
let { errors: f } =
|
|
290
|
+
const n = await Promise.all(
|
|
291
|
+
t.validators.filter((v) => d(v) !== void 0).map((v) => d(v).validate(e.data))
|
|
292
|
+
), l = n.every((v) => v.isValid);
|
|
293
|
+
let { errors: f } = m;
|
|
295
294
|
if (!l) {
|
|
296
|
-
const
|
|
297
|
-
f =
|
|
295
|
+
const v = n.map((P) => P.errors);
|
|
296
|
+
f = R(...v);
|
|
298
297
|
}
|
|
299
298
|
return {
|
|
300
299
|
errors: f,
|
|
301
300
|
isValid: l
|
|
302
301
|
};
|
|
303
302
|
}
|
|
304
|
-
const o = (
|
|
305
|
-
t.errors =
|
|
306
|
-
},
|
|
307
|
-
const
|
|
308
|
-
return o(
|
|
309
|
-
isValid: !
|
|
303
|
+
const o = (n) => {
|
|
304
|
+
t.errors = R(d(r.errors) ?? m.errors, n);
|
|
305
|
+
}, h = async () => {
|
|
306
|
+
const n = await a();
|
|
307
|
+
return o(n.errors), t.isValidated = !0, {
|
|
308
|
+
isValid: !C(n.errors),
|
|
310
309
|
errors: t.errors
|
|
311
310
|
};
|
|
312
|
-
}, c = u(() => !
|
|
311
|
+
}, c = u(() => !C(t.errors));
|
|
313
312
|
return {
|
|
314
|
-
...
|
|
315
|
-
validateForm:
|
|
313
|
+
...N(t),
|
|
314
|
+
validateForm: h,
|
|
316
315
|
defineValidator: s,
|
|
317
316
|
isValid: c
|
|
318
317
|
};
|
|
319
318
|
}
|
|
320
|
-
class
|
|
319
|
+
class dr {
|
|
321
320
|
constructor(r, t) {
|
|
322
321
|
this.path = r, this.validator = t;
|
|
323
322
|
}
|
|
324
323
|
async validate(r) {
|
|
325
324
|
const t = w(r, this.path);
|
|
326
325
|
if (!this.validator)
|
|
327
|
-
return
|
|
326
|
+
return m;
|
|
328
327
|
const s = await this.validator.validate(t);
|
|
329
328
|
return {
|
|
330
329
|
isValid: s.isValid,
|
|
@@ -340,85 +339,85 @@ class ir {
|
|
|
340
339
|
};
|
|
341
340
|
}
|
|
342
341
|
}
|
|
343
|
-
function
|
|
344
|
-
const s =
|
|
345
|
-
...
|
|
346
|
-
path: u(() => d(
|
|
347
|
-
setData: (
|
|
348
|
-
|
|
342
|
+
function fr(e, r, t) {
|
|
343
|
+
const s = U(e.data, r), a = u(() => w(e.initialData.value, r)), o = (i) => ({
|
|
344
|
+
...i,
|
|
345
|
+
path: u(() => d(i.path).replace(r + ".", "")),
|
|
346
|
+
setData: (p) => {
|
|
347
|
+
i.setData(p);
|
|
349
348
|
}
|
|
350
|
-
}),
|
|
351
|
-
const
|
|
352
|
-
return
|
|
353
|
-
}, c = (
|
|
354
|
-
const
|
|
355
|
-
...
|
|
356
|
-
path:
|
|
349
|
+
}), h = (i) => {
|
|
350
|
+
const p = F(r, i), V = e.getField(p);
|
|
351
|
+
return V ? o(V) : {};
|
|
352
|
+
}, c = (i) => {
|
|
353
|
+
const p = F(r, i.path), V = e.defineField({
|
|
354
|
+
...i,
|
|
355
|
+
path: p
|
|
357
356
|
});
|
|
358
|
-
return o(
|
|
359
|
-
},
|
|
360
|
-
const
|
|
361
|
-
return
|
|
362
|
-
}).map((
|
|
363
|
-
const
|
|
364
|
-
return
|
|
365
|
-
}), f = u(() => l().some((
|
|
357
|
+
return o(V);
|
|
358
|
+
}, n = () => e.getFields().filter((i) => {
|
|
359
|
+
const p = i.path.value;
|
|
360
|
+
return p.startsWith(r + ".") || p === r;
|
|
361
|
+
}).map((i) => o(i)), l = () => e.getFields().filter((i) => {
|
|
362
|
+
const p = i.path.value;
|
|
363
|
+
return p.startsWith(r + ".") || p === r;
|
|
364
|
+
}), f = u(() => l().some((i) => i.dirty.value)), v = u(() => l().some((i) => i.touched.value)), P = u(() => e.isValid.value), I = u(() => e.isValidated.value), J = u(() => rr(d(e.errors), r));
|
|
366
365
|
return {
|
|
367
366
|
data: s,
|
|
368
367
|
initialData: a,
|
|
369
368
|
defineField: c,
|
|
370
|
-
getField:
|
|
371
|
-
getFields:
|
|
369
|
+
getField: h,
|
|
370
|
+
getFields: n,
|
|
372
371
|
isDirty: f,
|
|
373
|
-
isTouched:
|
|
374
|
-
isValid:
|
|
375
|
-
isValidated:
|
|
376
|
-
errors:
|
|
377
|
-
defineValidator: (
|
|
378
|
-
const
|
|
379
|
-
() => new
|
|
372
|
+
isTouched: v,
|
|
373
|
+
isValid: P,
|
|
374
|
+
isValidated: I,
|
|
375
|
+
errors: J,
|
|
376
|
+
defineValidator: (i) => {
|
|
377
|
+
const p = z(i) ? i : b(i), V = u(
|
|
378
|
+
() => new dr(r, d(p))
|
|
380
379
|
);
|
|
381
|
-
return e.defineValidator(
|
|
380
|
+
return e.defineValidator(V), p;
|
|
382
381
|
},
|
|
383
|
-
reset: () => l().forEach((
|
|
382
|
+
reset: () => l().forEach((i) => i.reset()),
|
|
384
383
|
validateForm: () => e.validateForm(),
|
|
385
|
-
getSubForm: (
|
|
386
|
-
const
|
|
384
|
+
getSubForm: (i, p) => {
|
|
385
|
+
const V = F(r, i);
|
|
387
386
|
return e.getSubForm(
|
|
388
|
-
|
|
389
|
-
|
|
387
|
+
V,
|
|
388
|
+
p
|
|
390
389
|
);
|
|
391
390
|
}
|
|
392
391
|
};
|
|
393
392
|
}
|
|
394
|
-
function
|
|
395
|
-
const r = u(() => Object.freeze(
|
|
393
|
+
function Fr(e) {
|
|
394
|
+
const r = u(() => Object.freeze(g(e.initialData))), t = W(g(r)), s = E({
|
|
396
395
|
initialData: r,
|
|
397
396
|
data: t
|
|
398
397
|
});
|
|
399
|
-
|
|
400
|
-
s.data =
|
|
398
|
+
y(r, (f) => {
|
|
399
|
+
s.data = g(f);
|
|
401
400
|
});
|
|
402
|
-
const a =
|
|
403
|
-
t.value =
|
|
401
|
+
const a = ur(s, e), o = tr(s, a), h = sr(o), c = () => {
|
|
402
|
+
t.value = g(r), o.getFields().forEach(
|
|
404
403
|
(f) => f.reset()
|
|
405
404
|
);
|
|
406
405
|
};
|
|
407
|
-
function
|
|
408
|
-
return
|
|
406
|
+
function n(f, v) {
|
|
407
|
+
return fr(l, f);
|
|
409
408
|
}
|
|
410
409
|
const l = {
|
|
411
410
|
...o,
|
|
412
411
|
...a,
|
|
413
|
-
...
|
|
412
|
+
...h,
|
|
414
413
|
reset: c,
|
|
415
|
-
getSubForm:
|
|
416
|
-
initialData:
|
|
417
|
-
data:
|
|
414
|
+
getSubForm: n,
|
|
415
|
+
initialData: D(s, "initialData"),
|
|
416
|
+
data: D(s, "data")
|
|
418
417
|
};
|
|
419
418
|
return l;
|
|
420
419
|
}
|
|
421
|
-
const
|
|
420
|
+
const Er = /* @__PURE__ */ S({
|
|
422
421
|
__name: "Field",
|
|
423
422
|
props: {
|
|
424
423
|
form: {},
|
|
@@ -430,11 +429,56 @@ const Vr = /* @__PURE__ */ B({
|
|
|
430
429
|
setup(e) {
|
|
431
430
|
const r = e, t = r.form.defineField({
|
|
432
431
|
path: r.path
|
|
433
|
-
}), s =
|
|
434
|
-
return (a, o) =>
|
|
432
|
+
}), s = E(t);
|
|
433
|
+
return (a, o) => j(a.$slots, "default", M(B(s)));
|
|
434
|
+
}
|
|
435
|
+
}), wr = /* @__PURE__ */ S({
|
|
436
|
+
inheritAttrs: !1,
|
|
437
|
+
__name: "FormFieldWrapper",
|
|
438
|
+
props: {
|
|
439
|
+
component: {},
|
|
440
|
+
componentProps: {},
|
|
441
|
+
form: {},
|
|
442
|
+
path: {}
|
|
443
|
+
},
|
|
444
|
+
setup(e) {
|
|
445
|
+
return (r, t) => {
|
|
446
|
+
const s = Q("Field");
|
|
447
|
+
return $(), O(s, {
|
|
448
|
+
form: r.form,
|
|
449
|
+
path: r.path
|
|
450
|
+
}, {
|
|
451
|
+
default: A(({ errors: a, data: o, setData: h }) => [
|
|
452
|
+
($(), O(X(r.component), Y({ ...r.componentProps, ...r.$attrs }, {
|
|
453
|
+
"model-value": o,
|
|
454
|
+
errors: a,
|
|
455
|
+
name: r.path,
|
|
456
|
+
"onUpdate:modelValue": h
|
|
457
|
+
}), {
|
|
458
|
+
default: A(() => [
|
|
459
|
+
j(r.$slots, "default")
|
|
460
|
+
]),
|
|
461
|
+
_: 2
|
|
462
|
+
}, 1040, ["model-value", "errors", "name", "onUpdate:modelValue"]))
|
|
463
|
+
]),
|
|
464
|
+
_: 3
|
|
465
|
+
}, 8, ["form", "path"]);
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}), Pr = /* @__PURE__ */ S({
|
|
469
|
+
__name: "FormPart",
|
|
470
|
+
props: {
|
|
471
|
+
form: {},
|
|
472
|
+
path: {}
|
|
473
|
+
},
|
|
474
|
+
setup(e) {
|
|
475
|
+
const r = e, t = u(() => r.form.getSubForm(r.path));
|
|
476
|
+
return (s, a) => j(s.$slots, "default", M(B({ subform: t.value })));
|
|
435
477
|
}
|
|
436
478
|
});
|
|
437
479
|
export {
|
|
438
|
-
|
|
439
|
-
|
|
480
|
+
Er as Field,
|
|
481
|
+
wr as FormFieldWrapper,
|
|
482
|
+
Pr as FormPart,
|
|
483
|
+
Fr as useForm
|
|
440
484
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ObjectOf, Paths } from './util';
|
|
2
|
+
import { Form } from './form';
|
|
3
|
+
import { DeepPartial } from '../utils/type-helpers';
|
|
4
|
+
export interface FormComponentProps<TData extends object, TPath extends Paths<TData>, TModelValueType> {
|
|
5
|
+
form: Form<TData & DeepPartial<ObjectOf<TPath, TModelValueType>>>;
|
|
6
|
+
path: TPath;
|
|
7
|
+
}
|
|
8
|
+
export type ExcludedFieldProps = 'modelValue' | 'onUpdate:modelValue' | 'errors';
|
package/dist/types/util.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ type CleanupAntiCollapse<T extends string> = T extends `${infer Left}${AntiColla
|
|
|
15
15
|
type RecursePaths<T, Seen = never> = T extends Seen ? never : T extends Array<infer ArrayType> ? `${number}` | `${number}.${RecursePaths<ArrayType, Seen | T>}` : T extends object ? {
|
|
16
16
|
[K in keyof T]-?: `${AntiCollapse}${Exclude<K, symbol>}${'' | `.${RecursePaths<T[K], Seen | T>}`}`;
|
|
17
17
|
}[keyof T] : never;
|
|
18
|
-
export type Paths<T, Seen = never> = CleanupAntiCollapse<RecursePaths<T, Seen
|
|
18
|
+
export type Paths<T, Seen = never> = CleanupAntiCollapse<RecursePaths<T, Seen>> | '';
|
|
19
19
|
/**
|
|
20
20
|
* Removes the last part of a dot-connected path.
|
|
21
21
|
*/
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamnovu/kit-vue-forms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@vueuse/core": "^13.5.0",
|
|
33
|
+
"vue-component-type-helpers": "^3.0.5",
|
|
32
34
|
"zod": "^4"
|
|
33
35
|
},
|
|
34
36
|
"scripts": {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Field
|
|
3
|
+
v-slot="{ errors, data, setData }"
|
|
4
|
+
:form="form"
|
|
5
|
+
:path="path"
|
|
6
|
+
>
|
|
7
|
+
<component
|
|
8
|
+
:is="component"
|
|
9
|
+
v-bind="{...componentProps, ...$attrs}"
|
|
10
|
+
:model-value="data"
|
|
11
|
+
:errors="errors"
|
|
12
|
+
:name="path"
|
|
13
|
+
@update:model-value="setData"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</component>
|
|
17
|
+
</Field>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script
|
|
21
|
+
setup
|
|
22
|
+
lang="ts"
|
|
23
|
+
generic="TData extends object, TPath extends Paths<TData>, TComponent extends Component"
|
|
24
|
+
>
|
|
25
|
+
import { type Component } from 'vue'
|
|
26
|
+
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
27
|
+
import type { Paths } from '../types/util.ts'
|
|
28
|
+
import type { Form } from '../types/form.ts'
|
|
29
|
+
|
|
30
|
+
export interface FormFieldWrapperProps<TData extends object, TPath extends string, TComponent> {
|
|
31
|
+
component: TComponent
|
|
32
|
+
componentProps: Omit<ComponentProps<TComponent>, 'modelValue' | 'update:modelValue' | 'errors'>
|
|
33
|
+
form: Form<TData>
|
|
34
|
+
path: TPath
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
defineOptions({
|
|
38
|
+
inheritAttrs: false,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
defineProps<FormFieldWrapperProps<TData, TPath, TComponent>>()
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot v-bind="{ subform }" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script
|
|
6
|
+
setup
|
|
7
|
+
lang="ts"
|
|
8
|
+
generic="TData extends object, TPath extends EntityPaths<TData>"
|
|
9
|
+
>
|
|
10
|
+
import { computed } from 'vue'
|
|
11
|
+
import type { Form } from '../types/form.ts'
|
|
12
|
+
import type { EntityPaths } from '../types/util.ts'
|
|
13
|
+
|
|
14
|
+
export interface FormPartProps<TData extends object, TPath> {
|
|
15
|
+
form: Form<TData>
|
|
16
|
+
path: TPath
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = defineProps<FormPartProps<TData, TPath>>()
|
|
20
|
+
|
|
21
|
+
const subform = computed(() => props.form.getSubForm(props.path))
|
|
22
|
+
</script>
|
|
@@ -7,7 +7,6 @@ import { useFieldRegistry } from './useFieldRegistry'
|
|
|
7
7
|
import { useFormState } from './useFormState'
|
|
8
8
|
import { createSubformInterface, type SubformOptions } from './useSubform'
|
|
9
9
|
import { useValidation, type ValidationOptions } from './useValidation'
|
|
10
|
-
import { syncRef } from '@vueuse/core'
|
|
11
10
|
|
|
12
11
|
// TODO @Elias implement validation strategy handling
|
|
13
12
|
|
package/src/index.ts
CHANGED
|
@@ -12,5 +12,14 @@ export type { DeepPartial } from './utils/type-helpers'
|
|
|
12
12
|
export type { Form, FormField } from './types/form'
|
|
13
13
|
export type { SplitPath, Paths, PickProps, ObjectOf, EntityPaths } from './types/util'
|
|
14
14
|
|
|
15
|
+
export type { FormComponentProps, ExcludedFieldProps } from './types/FormComponentProps'
|
|
16
|
+
|
|
17
|
+
// Vue Components
|
|
15
18
|
export { default as Field } from './components/Field.vue'
|
|
16
19
|
export type { FieldProps } from './components/Field.vue'
|
|
20
|
+
|
|
21
|
+
export { default as FormFieldWrapper } from './components/FormFieldWrapper.vue'
|
|
22
|
+
export type { FormFieldWrapperProps } from './components/FormFieldWrapper.vue'
|
|
23
|
+
|
|
24
|
+
export { default as FormPart } from './components/FormPart.vue'
|
|
25
|
+
export type { FormPartProps } from './components/FormPart.vue'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ObjectOf, Paths } from './util'
|
|
2
|
+
import type { Form } from './form'
|
|
3
|
+
import type { DeepPartial } from '../utils/type-helpers'
|
|
4
|
+
|
|
5
|
+
export interface FormComponentProps<
|
|
6
|
+
TData extends object,
|
|
7
|
+
TPath extends Paths<TData>,
|
|
8
|
+
TModelValueType,
|
|
9
|
+
> {
|
|
10
|
+
form: Form<TData & DeepPartial<ObjectOf<TPath, TModelValueType>>>
|
|
11
|
+
path: TPath
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ExcludedFieldProps = 'modelValue' | 'onUpdate:modelValue' | 'errors'
|
package/src/types/util.ts
CHANGED
|
@@ -59,7 +59,7 @@ type RecursePaths<T, Seen = never> =
|
|
|
59
59
|
}[keyof T]
|
|
60
60
|
: never
|
|
61
61
|
|
|
62
|
-
export type Paths<T, Seen = never> = CleanupAntiCollapse<RecursePaths<T, Seen>>
|
|
62
|
+
export type Paths<T, Seen = never> = CleanupAntiCollapse<RecursePaths<T, Seen>> | ''
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Removes the last part of a dot-connected path.
|
package/tests/formState.test.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { reactive } from 'vue'
|
|
|
3
3
|
import { useFieldRegistry } from '../src/composables/useFieldRegistry'
|
|
4
4
|
import { useFormState } from '../src/composables/useFormState'
|
|
5
5
|
import { useForm } from '../src'
|
|
6
|
+
import { useValidation } from '../src/composables/useValidation'
|
|
6
7
|
|
|
7
8
|
describe('useFormState', () => {
|
|
8
9
|
it('should detect dirty state when form data changes', () => {
|
|
@@ -45,10 +46,12 @@ describe('useFormState', () => {
|
|
|
45
46
|
email: 'john@example.com',
|
|
46
47
|
}
|
|
47
48
|
const data = reactive(initialData)
|
|
48
|
-
const
|
|
49
|
+
const state = reactive({
|
|
49
50
|
data,
|
|
50
51
|
initialData,
|
|
51
52
|
})
|
|
53
|
+
const validationState = useValidation(state, {})
|
|
54
|
+
const fields = useFieldRegistry(state, validationState)
|
|
52
55
|
|
|
53
56
|
const nameField = fields.defineField({ path: 'name' })
|
|
54
57
|
fields.defineField({ path: 'email' })
|
|
@@ -62,12 +65,16 @@ describe('useFormState', () => {
|
|
|
62
65
|
})
|
|
63
66
|
|
|
64
67
|
it('should handle empty fields map', () => {
|
|
65
|
-
const initialData = {
|
|
68
|
+
const initialData = {
|
|
69
|
+
name: 'John',
|
|
70
|
+
}
|
|
66
71
|
const data = reactive(initialData)
|
|
67
|
-
const
|
|
72
|
+
const state = reactive({
|
|
68
73
|
data,
|
|
69
74
|
initialData,
|
|
70
75
|
})
|
|
76
|
+
const validationState = useValidation(state, {})
|
|
77
|
+
const fields = useFieldRegistry(state, validationState)
|
|
71
78
|
|
|
72
79
|
const formState = useFormState(fields)
|
|
73
80
|
|
package/tests/subform.test.ts
CHANGED
|
@@ -1345,4 +1345,23 @@ describe('Subform Implementation', () => {
|
|
|
1345
1345
|
})
|
|
1346
1346
|
})
|
|
1347
1347
|
})
|
|
1348
|
+
|
|
1349
|
+
describe('Toplevel array subform', () => {
|
|
1350
|
+
it('can handle arrays on top level', async () => {
|
|
1351
|
+
const schema = z.array(z.string())
|
|
1352
|
+
|
|
1353
|
+
const form = useForm({
|
|
1354
|
+
initialData: { test: ['item1', 'item2'] },
|
|
1355
|
+
schema,
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
const subform = form.getSubForm('test')
|
|
1359
|
+
|
|
1360
|
+
const rootField = subform.defineField({ path: '' })
|
|
1361
|
+
const itemField = subform.defineField({ path: '0' })
|
|
1362
|
+
|
|
1363
|
+
expect(rootField.data.value).toEqual(['item1', 'item2'])
|
|
1364
|
+
expect(itemField.data.value).toEqual('item1')
|
|
1365
|
+
})
|
|
1366
|
+
})
|
|
1348
1367
|
})
|
package/tests/useForm.test.ts
CHANGED
|
@@ -193,4 +193,26 @@ describe('useForm', () => {
|
|
|
193
193
|
expect(form.errors.value.general).toEqual([])
|
|
194
194
|
expect(form.errors.value.propertyErrors).toEqual({})
|
|
195
195
|
})
|
|
196
|
+
|
|
197
|
+
it('can handle arrays on top level', async () => {
|
|
198
|
+
const schema = z.array(z.string())
|
|
199
|
+
|
|
200
|
+
const form = useForm({
|
|
201
|
+
initialData: ['item1', 'item2'],
|
|
202
|
+
schema,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const result = await form.validateForm()
|
|
206
|
+
|
|
207
|
+
expect(result.isValid).toBe(true)
|
|
208
|
+
expect(form.isValidated.value).toBe(true)
|
|
209
|
+
expect(form.errors.value.general).toEqual([])
|
|
210
|
+
expect(form.errors.value.propertyErrors).toEqual({})
|
|
211
|
+
|
|
212
|
+
const rootField = form.defineField({ path: '' })
|
|
213
|
+
const itemField = form.defineField({ path: '1' })
|
|
214
|
+
|
|
215
|
+
expect(rootField.data.value).toEqual(['item1', 'item2'])
|
|
216
|
+
expect(itemField.data.value).toBe('item2')
|
|
217
|
+
})
|
|
196
218
|
})
|